From 8246a0275844bc7f22695ac2f55b6ed7b374a8ab Mon Sep 17 00:00:00 2001 From: Takuma Igarashi Date: Tue, 26 Apr 2022 19:30:03 +0900 Subject: [PATCH 1/2] feat: modify QueryParameter to return not string but URLSearchParams --- src/CookieParameter.ts | 35 +++++++++++-- src/Core.ts | 74 +++++++++++++++++---------- src/QueryParameter.ts | 2 +- src/__tests__/CookieParameter-test.ts | 4 +- src/__tests__/DeepObject-test.ts | 6 +-- src/__tests__/QueryParameter-test.ts | 30 +++++------ 6 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/CookieParameter.ts b/src/CookieParameter.ts index 6d53ebe..8a6ca4e 100644 --- a/src/CookieParameter.ts +++ b/src/CookieParameter.ts @@ -1,5 +1,5 @@ -import { PrimitiveType, ObjectType, ArrayType } from "./Types"; -import * as Core from "./Core"; +import { PrimitiveType, ObjectType, ArrayType, ParameterOfForm } from "./Types"; +import * as Guard from "./Guard"; export interface Parameter { value: PrimitiveType | ObjectType | ArrayType; @@ -9,7 +9,36 @@ export interface Parameter { export const generate = (key: string | number, params: Parameter): string | undefined => { if (params.style === "form") { - return Core.generateFormParamter(key, params); + return generateFormParamter(key, params); } return undefined; }; + +const generateFormParamter = (key: string | number, params: ParameterOfForm): string => { + if (Guard.isEmpty(params.value)) { + return `${key}=`; + } + if (Guard.isPrimitive(params.value)) { + return `${key}=${params.value}`; + } + if (Guard.isArray(params.value)) { + if (params.explode) { + return params.value.map(item => `${key}=${item}`).join(";"); + } else { + return `${key}=${params.value.join(",")}`; + } + } + if (Guard.isObject(params.value)) { + if (params.explode) { + return Object.entries(params.value) + .map(([k, v]) => `${k}=${v}`) + .join(";"); + } else { + const value = Object.entries(params.value) + .map(([k, v]) => `${k},${v}`) + .join(","); + return `${key}=${value}`; + } + } + return `${key}=`; +}; diff --git a/src/Core.ts b/src/Core.ts index bc36b20..aa554f1 100644 --- a/src/Core.ts +++ b/src/Core.ts @@ -36,75 +36,97 @@ export const generateFromSimple = (key: string | number, params: ParameterOfSimp return undefined; }; -export const generateFormParamter = (key: string | number, params: ParameterOfForm): string => { +export const generateFormParamter = (key: string | number, params: ParameterOfForm): URLSearchParams | undefined => { + const queryParams = new URLSearchParams(); if (Guard.isEmpty(params.value)) { - return `${key}=`; + queryParams.append(key.toString(), ""); + return queryParams; } + if (Guard.isPrimitive(params.value)) { - return `${key}=${params.value}`; + queryParams.append(key.toString(), params.value?.toString() ?? ""); + return queryParams; } + if (Guard.isArray(params.value)) { if (params.explode) { - return params.value.map(item => `${key}=${item}`).join("&"); + params.value.map(item => { + queryParams.append(key.toString(), item.toString()); + }); } else { - return `${key}=${params.value.join(",")}`; + queryParams.append(key.toString(), params.value.join(",")); } + return queryParams; } + if (Guard.isObject(params.value)) { if (params.explode) { - return Object.entries(params.value) - .map(([k, v]) => `${k}=${v}`) - .join("&"); + Object.entries(params.value).map(([k, v]) => { + queryParams.append(k.toString(), v.toString()); + }); } else { const value = Object.entries(params.value) .map(([k, v]) => `${k},${v}`) .join(","); - return `${key}=${value}`; + queryParams.append(key.toString(), value); } + return queryParams; } - return `${key}=`; + + queryParams.append(key.toString(), ""); + return queryParams; }; -export const generateSpaceDelimited = (key: string | number, params: ParameterOfSpaceDelimited): string | undefined => { +export const generateSpaceDelimited = (key: string | number, params: ParameterOfSpaceDelimited): URLSearchParams | undefined => { + const queryParams = new URLSearchParams(); if (Guard.isArray(params.value)) { - return encodeURIComponent(params.value.join(" ")); + queryParams.append(key.toString(), params.value.join(" ")); + return queryParams; } + if (Guard.isObject(params.value)) { const value = Object.entries(params.value) .map(([k, v]) => `${k} ${v}`) .join(" "); - return encodeURIComponent(value); + queryParams.append(key.toString(), value); + return queryParams; } + return undefined; }; -export const generatePipeDelimitedParameter = (key: string | number, params: ParameterOfPipeDelimited): string | undefined => { +export const generatePipeDelimitedParameter = (key: string | number, params: ParameterOfPipeDelimited): URLSearchParams | undefined => { + const queryParams = new URLSearchParams(); if (Guard.isArray(params.value)) { - return params.value.join("|"); + queryParams.append(key.toString(), params.value.join("|")); + return queryParams; } + if (Guard.isObject(params.value)) { const value = Object.entries(params.value) .map(([k, v]) => `${k}|${v}`) .join("|"); - return value; + queryParams.append(key.toString(), value); + return queryParams; } + return undefined; }; -export const generateDeepObjectParameter = (key: string | number, params: ParameterOfDeepObject): string | undefined => { +export const generateDeepObjectParameter = (key: string | number, params: ParameterOfDeepObject): URLSearchParams | undefined => { if (!Guard.isObject(params.value)) { return undefined; } + const queryParams = new URLSearchParams(); const flatObject = flatten(params.value); - return Object.entries(flatObject) - .map(([dotKeyName, primitiveValue]) => { - const nestedKey = dotKeyName - .split(".") - .map(k1 => `[${k1}]`) - .join(""); - return `${[key]}${nestedKey}=${primitiveValue}`; - }) - .join("&"); + Object.entries(flatObject).map(([dotKeyName, primitiveValue]) => { + const nestedKey = dotKeyName + .split(".") + .map(k1 => `[${k1}]`) + .join(""); + queryParams.append(`${key}${nestedKey}`, primitiveValue?.toString() ?? ""); + }); + return queryParams; }; export const generateFromMatrix = (key: string | number, params: ParameterOfMatrix): string | undefined => { diff --git a/src/QueryParameter.ts b/src/QueryParameter.ts index a20769c..0d00a62 100644 --- a/src/QueryParameter.ts +++ b/src/QueryParameter.ts @@ -3,7 +3,7 @@ import * as Core from "./Core"; export type Parameter = ParameterOfForm | ParameterOfSpaceDelimited | ParameterOfPipeDelimited | ParameterOfDeepObject; -export const generate = (key: string | number, params: Parameter): string | undefined => { +export const generate = (key: string | number, params: Parameter): URLSearchParams | undefined => { if (params.style === "form") { return Core.generateFormParamter(key, params); } diff --git a/src/__tests__/CookieParameter-test.ts b/src/__tests__/CookieParameter-test.ts index 7e4ce04..b2c8c15 100644 --- a/src/__tests__/CookieParameter-test.ts +++ b/src/__tests__/CookieParameter-test.ts @@ -55,7 +55,7 @@ describe("CookieParameter - style:form", () => { style: "form", explode: true, }); - expect(result).toBe("color=blue&color=black&color=brown"); + expect(result).toBe("color=blue;color=black;color=brown"); }); test("explode:false value:object", () => { const result1 = CookieParameter.generate("color", { @@ -79,6 +79,6 @@ describe("CookieParameter - style:form", () => { style: "form", explode: true, }); - expect(result1).toBe("R=100&G=200&B=150"); + expect(result1).toBe("R=100;G=200;B=150"); }); }); diff --git a/src/__tests__/DeepObject-test.ts b/src/__tests__/DeepObject-test.ts index af4a11e..13593c5 100644 --- a/src/__tests__/DeepObject-test.ts +++ b/src/__tests__/DeepObject-test.ts @@ -7,7 +7,7 @@ describe("QueryParameter - style:deepObject", () => { style: "deepObject", explode: true, }); - expect(result1).toBe(`filters[level1][level2]=hello`); + expect(result1?.toString()).toBe(`filters%5Blevel1%5D%5Blevel2%5D=hello`); }); test("explode:true / nest value", () => { const result1 = QueryParameter.generate("filters", { @@ -15,7 +15,7 @@ describe("QueryParameter - style:deepObject", () => { style: "deepObject", explode: true, }); - expect(result1).toBe(`filters[level1][level2][level3]=hello`); + expect(result1?.toString()).toBe(`filters%5Blevel1%5D%5Blevel2%5D%5Blevel3%5D=hello`); }); test("explode:true / nest value", () => { const result1 = QueryParameter.generate("filters", { @@ -23,6 +23,6 @@ describe("QueryParameter - style:deepObject", () => { style: "deepObject", explode: true, }); - expect(result1).toBe(`filters[level1][level2][level3]=hello&filters[level1][level2-1][level3-1]=world`); + expect(result1?.toString()).toBe(`filters%5Blevel1%5D%5Blevel2%5D%5Blevel3%5D=hello&filters%5Blevel1%5D%5Blevel2-1%5D%5Blevel3-1%5D=world`); }); }); diff --git a/src/__tests__/QueryParameter-test.ts b/src/__tests__/QueryParameter-test.ts index 9ad4cfd..be1375a 100644 --- a/src/__tests__/QueryParameter-test.ts +++ b/src/__tests__/QueryParameter-test.ts @@ -7,25 +7,25 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1).toBe("color="); + expect(result1?.toString()).toBe("color="); const result2 = QueryParameter.generate("color", { value: [], style: "form", explode: true, }); - expect(result2).toBe("color="); + expect(result2?.toString()).toBe("color="); const result3 = QueryParameter.generate("color", { value: undefined, style: "form", explode: false, }); - expect(result3).toBe("color="); + expect(result3?.toString()).toBe("color="); const result4 = QueryParameter.generate("color", { value: undefined, style: "form", explode: true, }); - expect(result4).toBe("color="); + expect(result4?.toString()).toBe("color="); }); test("explode:true/false value:string", () => { const result1 = QueryParameter.generate("color", { @@ -33,13 +33,13 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1).toBe("color=blue"); + expect(result1?.toString()).toBe("color=blue"); const result2 = QueryParameter.generate("color", { value: "blue", style: "form", explode: true, }); - expect(result2).toBe("color=blue"); + expect(result2?.toString()).toBe("color=blue"); }); test("explode:false value:string[]", () => { const result = QueryParameter.generate("color", { @@ -47,7 +47,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result).toBe("color=blue,black,brown"); + expect(result?.toString()).toBe("color=blue%2Cblack%2Cbrown"); }); test("explode:true value:string[]", () => { const result = QueryParameter.generate("color", { @@ -55,7 +55,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: true, }); - expect(result).toBe("color=blue&color=black&color=brown"); + expect(result?.toString()).toBe("color=blue&color=black&color=brown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -67,7 +67,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1).toBe("color=R,100,G,200,B,150"); + expect(result1?.toString()).toBe("color=R%2C100%2CG%2C200%2CB%2C150"); }); test("explode:true value:object", () => { const result1 = QueryParameter.generate("color", { @@ -79,7 +79,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: true, }); - expect(result1).toBe("R=100&G=200&B=150"); + expect(result1?.toString()).toBe("R=100&G=200&B=150"); }); }); @@ -90,7 +90,7 @@ describe("QueryParameter - style:spaceDelimited", () => { style: "spaceDelimited", explode: false, }); - expect(result1).toBe("blue%20black%20brown"); + expect(result1?.toString()).toBe("color=blue+black+brown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -102,7 +102,7 @@ describe("QueryParameter - style:spaceDelimited", () => { style: "spaceDelimited", explode: false, }); - expect(result1).toBe("R%20100%20G%20200%20B%20150"); + expect(result1?.toString()).toBe("color=R+100+G+200+B+150"); }); }); @@ -113,7 +113,7 @@ describe("QueryParameter - style:pipeDelimited", () => { style: "pipeDelimited", explode: false, }); - expect(result1).toBe("blue|black|brown"); + expect(result1?.toString()).toBe("color=blue%7Cblack%7Cbrown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -125,7 +125,7 @@ describe("QueryParameter - style:pipeDelimited", () => { style: "pipeDelimited", explode: false, }); - expect(result1).toBe("R|100|G|200|B|150"); + expect(result1?.toString()).toBe("color=R%7C100%7CG%7C200%7CB%7C150"); }); }); @@ -140,6 +140,6 @@ describe("QueryParameter - style:deepObject", () => { style: "deepObject", explode: true, }); - expect(result1).toBe("color[R]=100&color[G]=200&color[B]=150"); + expect(result1?.toString()).toBe("color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150"); }); }); From a5dbc079c1eeaa93df85fe10af06de00b93ceb39 Mon Sep 17 00:00:00 2001 From: Takuma Igarashi Date: Thu, 28 Apr 2022 19:06:42 +0900 Subject: [PATCH 2/2] fix: devide generate method --- src/Core.ts | 33 ++++++++++++++++++++++++---- src/QueryParameter.ts | 18 ++++++++++++++- src/__tests__/QueryParameter-test.ts | 30 ++++++++++++------------- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/Core.ts b/src/Core.ts index aa554f1..2e22780 100644 --- a/src/Core.ts +++ b/src/Core.ts @@ -36,7 +36,11 @@ export const generateFromSimple = (key: string | number, params: ParameterOfSimp return undefined; }; -export const generateFormParamter = (key: string | number, params: ParameterOfForm): URLSearchParams | undefined => { +export const generateFormParamter = (key: string | number, params: ParameterOfForm): string => { + return generateFormParamterAsURLSearchParams(key, params).toString(); +}; + +export const generateFormParamterAsURLSearchParams = (key: string | number, params: ParameterOfForm): URLSearchParams => { const queryParams = new URLSearchParams(); if (Guard.isEmpty(params.value)) { queryParams.append(key.toString(), ""); @@ -77,7 +81,14 @@ export const generateFormParamter = (key: string | number, params: ParameterOfFo return queryParams; }; -export const generateSpaceDelimited = (key: string | number, params: ParameterOfSpaceDelimited): URLSearchParams | undefined => { +export const generateSpaceDelimited = (key: string | number, params: ParameterOfSpaceDelimited): string | undefined => { + return generateSpaceDelimitedAsURLSearchParams(key, params)?.toString(); +}; + +export const generateSpaceDelimitedAsURLSearchParams = ( + key: string | number, + params: ParameterOfSpaceDelimited, +): URLSearchParams | undefined => { const queryParams = new URLSearchParams(); if (Guard.isArray(params.value)) { queryParams.append(key.toString(), params.value.join(" ")); @@ -95,7 +106,14 @@ export const generateSpaceDelimited = (key: string | number, params: ParameterOf return undefined; }; -export const generatePipeDelimitedParameter = (key: string | number, params: ParameterOfPipeDelimited): URLSearchParams | undefined => { +export const generatePipeDelimitedParameter = (key: string | number, params: ParameterOfPipeDelimited): string | undefined => { + return generatePipeDelimitedParameterAsURLSearchParams(key, params)?.toString(); +}; + +export const generatePipeDelimitedParameterAsURLSearchParams = ( + key: string | number, + params: ParameterOfPipeDelimited, +): URLSearchParams | undefined => { const queryParams = new URLSearchParams(); if (Guard.isArray(params.value)) { queryParams.append(key.toString(), params.value.join("|")); @@ -113,7 +131,14 @@ export const generatePipeDelimitedParameter = (key: string | number, params: Par return undefined; }; -export const generateDeepObjectParameter = (key: string | number, params: ParameterOfDeepObject): URLSearchParams | undefined => { +export const generateDeepObjectParameter = (key: string | number, params: ParameterOfDeepObject): string | undefined => { + return generateDeepObjectParameterAsURLSearchParams(key, params)?.toString(); +}; + +export const generateDeepObjectParameterAsURLSearchParams = ( + key: string | number, + params: ParameterOfDeepObject, +): URLSearchParams | undefined => { if (!Guard.isObject(params.value)) { return undefined; } diff --git a/src/QueryParameter.ts b/src/QueryParameter.ts index 0d00a62..937efff 100644 --- a/src/QueryParameter.ts +++ b/src/QueryParameter.ts @@ -3,7 +3,7 @@ import * as Core from "./Core"; export type Parameter = ParameterOfForm | ParameterOfSpaceDelimited | ParameterOfPipeDelimited | ParameterOfDeepObject; -export const generate = (key: string | number, params: Parameter): URLSearchParams | undefined => { +export const generate = (key: string | number, params: Parameter): string | undefined => { if (params.style === "form") { return Core.generateFormParamter(key, params); } @@ -18,3 +18,19 @@ export const generate = (key: string | number, params: Parameter): URLSearchPara } return undefined; }; + +export const generateAsURLSearchParams = (key: string | number, params: Parameter): URLSearchParams | undefined => { + if (params.style === "form") { + return Core.generateFormParamterAsURLSearchParams(key, params); + } + if (params.style === "spaceDelimited") { + return Core.generateSpaceDelimitedAsURLSearchParams(key, params); + } + if (params.style === "pipeDelimited") { + return Core.generatePipeDelimitedParameterAsURLSearchParams(key, params); + } + if (params.style === "deepObject") { + return Core.generateDeepObjectParameterAsURLSearchParams(key, params); + } + return undefined; +}; diff --git a/src/__tests__/QueryParameter-test.ts b/src/__tests__/QueryParameter-test.ts index be1375a..4e47175 100644 --- a/src/__tests__/QueryParameter-test.ts +++ b/src/__tests__/QueryParameter-test.ts @@ -7,25 +7,25 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1?.toString()).toBe("color="); + expect(result1).toBe("color="); const result2 = QueryParameter.generate("color", { value: [], style: "form", explode: true, }); - expect(result2?.toString()).toBe("color="); + expect(result2).toBe("color="); const result3 = QueryParameter.generate("color", { value: undefined, style: "form", explode: false, }); - expect(result3?.toString()).toBe("color="); + expect(result3).toBe("color="); const result4 = QueryParameter.generate("color", { value: undefined, style: "form", explode: true, }); - expect(result4?.toString()).toBe("color="); + expect(result4).toBe("color="); }); test("explode:true/false value:string", () => { const result1 = QueryParameter.generate("color", { @@ -33,13 +33,13 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1?.toString()).toBe("color=blue"); + expect(result1).toBe("color=blue"); const result2 = QueryParameter.generate("color", { value: "blue", style: "form", explode: true, }); - expect(result2?.toString()).toBe("color=blue"); + expect(result2).toBe("color=blue"); }); test("explode:false value:string[]", () => { const result = QueryParameter.generate("color", { @@ -47,7 +47,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result?.toString()).toBe("color=blue%2Cblack%2Cbrown"); + expect(result).toBe("color=blue%2Cblack%2Cbrown"); }); test("explode:true value:string[]", () => { const result = QueryParameter.generate("color", { @@ -55,7 +55,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: true, }); - expect(result?.toString()).toBe("color=blue&color=black&color=brown"); + expect(result).toBe("color=blue&color=black&color=brown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -67,7 +67,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: false, }); - expect(result1?.toString()).toBe("color=R%2C100%2CG%2C200%2CB%2C150"); + expect(result1).toBe("color=R%2C100%2CG%2C200%2CB%2C150"); }); test("explode:true value:object", () => { const result1 = QueryParameter.generate("color", { @@ -79,7 +79,7 @@ describe("QueryParameter - style:form", () => { style: "form", explode: true, }); - expect(result1?.toString()).toBe("R=100&G=200&B=150"); + expect(result1).toBe("R=100&G=200&B=150"); }); }); @@ -90,7 +90,7 @@ describe("QueryParameter - style:spaceDelimited", () => { style: "spaceDelimited", explode: false, }); - expect(result1?.toString()).toBe("color=blue+black+brown"); + expect(result1).toBe("color=blue+black+brown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -102,7 +102,7 @@ describe("QueryParameter - style:spaceDelimited", () => { style: "spaceDelimited", explode: false, }); - expect(result1?.toString()).toBe("color=R+100+G+200+B+150"); + expect(result1).toBe("color=R+100+G+200+B+150"); }); }); @@ -113,7 +113,7 @@ describe("QueryParameter - style:pipeDelimited", () => { style: "pipeDelimited", explode: false, }); - expect(result1?.toString()).toBe("color=blue%7Cblack%7Cbrown"); + expect(result1).toBe("color=blue%7Cblack%7Cbrown"); }); test("explode:false value:object", () => { const result1 = QueryParameter.generate("color", { @@ -125,7 +125,7 @@ describe("QueryParameter - style:pipeDelimited", () => { style: "pipeDelimited", explode: false, }); - expect(result1?.toString()).toBe("color=R%7C100%7CG%7C200%7CB%7C150"); + expect(result1).toBe("color=R%7C100%7CG%7C200%7CB%7C150"); }); }); @@ -140,6 +140,6 @@ describe("QueryParameter - style:deepObject", () => { style: "deepObject", explode: true, }); - expect(result1?.toString()).toBe("color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150"); + expect(result1).toBe("color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150"); }); });