Skip to content

QueryParameter returns URLSearchParams value #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions src/CookieParameter.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらの関数利用したい場合はテストされるようにexportし、テストを実施してください。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

把握です

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(";");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CookieParameterはURLSearchParams使うの良くなさそうなので旧実装もってきて分離しました。
explode: true の場合の区切り文字は ; の方が適切な気がしたので変更しています。
Coreの方にあった方がよければそちらに移します。

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: formexplode: true でINPUTがobjectの場合以下の行に該当します。

image

RFC6570上で特にCookie Parameterについて言及を自分は見つけられませんでした。他にこの変更をサポートする仕様はありますか?

参考

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explode: true がそれぞれの変数を個別に展開して設定する、という意味であると解釈するとCookieの仕様 RFC6265 に沿ったフォーマットの方が適切であると考えます。
が、おっしゃる通りCookie Parameterにおいて style: form explode: true の仕様が明確になっておらず混乱の元となっています。

個人的にはこの仕様のCookie Parameterは使わないのでこの変更は不要ですが、上記OpenAPIのexample tableを守るなら & 区切りに戻します。

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このPRはQueryParameterだけにフォーカスして、必要になったらCookie Parameterの方も調整しましょう 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解です

} else {
const value = Object.entries(params.value)
.map(([k, v]) => `${k},${v}`)
.join(",");
return `${key}=${value}`;
}
}
return `${key}=`;
};
91 changes: 69 additions & 22 deletions src/Core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,74 +37,121 @@ export const generateFromSimple = (key: string | number, params: ParameterOfSimp
};

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)) {
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 => {
return generateSpaceDelimitedAsURLSearchParams(key, params)?.toString();
};

export const generateSpaceDelimitedAsURLSearchParams = (
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 => {
return generatePipeDelimitedParameterAsURLSearchParams(key, params)?.toString();
};

export const generatePipeDelimitedParameterAsURLSearchParams = (
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 => {
return generateDeepObjectParameterAsURLSearchParams(key, params)?.toString();
};

export const generateDeepObjectParameterAsURLSearchParams = (
key: string | number,
params: ParameterOfDeepObject,
): URLSearchParams | undefined => {
if (!Guard.isObject(params.value)) {
return undefined;
}
const queryParams = new URLSearchParams();
const flatObject = flatten<ObjectType, { [key: string]: PrimitiveType }>(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 => {
Expand Down
16 changes: 16 additions & 0 deletions src/QueryParameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,19 @@ export const generate = (key: string | number, params: Parameter): string | unde
}
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;
};
4 changes: 2 additions & 2 deletions src/__tests__/CookieParameter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand All @@ -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");
});
});
6 changes: 3 additions & 3 deletions src/__tests__/DeepObject-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ 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", {
value: { level1: { level2: { level3: "hello" } } },
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", {
value: { level1: { level2: { level3: "hello" }, "level2-1": { "level3-1": "world" } } },
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`);
});
});
14 changes: 7 additions & 7 deletions src/__tests__/QueryParameter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("QueryParameter - style:form", () => {
style: "form",
explode: false,
});
expect(result).toBe("color=blue,black,brown");
expect(result).toBe("color=blue%2Cblack%2Cbrown");
});
test("explode:true value:string[]", () => {
const result = QueryParameter.generate("color", {
Expand All @@ -67,7 +67,7 @@ describe("QueryParameter - style:form", () => {
style: "form",
explode: false,
});
expect(result1).toBe("color=R,100,G,200,B,150");
expect(result1).toBe("color=R%2C100%2CG%2C200%2CB%2C150");
});
test("explode:true value:object", () => {
const result1 = QueryParameter.generate("color", {
Expand All @@ -90,7 +90,7 @@ describe("QueryParameter - style:spaceDelimited", () => {
style: "spaceDelimited",
explode: false,
});
expect(result1).toBe("blue%20black%20brown");
expect(result1).toBe("color=blue+black+brown");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここはOpenAPI Formatterの仕様と異なるようです。

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect(result1).toBe("color=blue%20black%20brown");

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://qiita.com/masakielastic/items/61f5d9a215c62b55ccf2
このような違いがあるようです。このライブラリはOpenAPI準拠で表すので%20に寄せます。

});
test("explode:false value:object", () => {
const result1 = QueryParameter.generate("color", {
Expand All @@ -102,7 +102,7 @@ describe("QueryParameter - style:spaceDelimited", () => {
style: "spaceDelimited",
explode: false,
});
expect(result1).toBe("R%20100%20G%20200%20B%20150");
expect(result1).toBe("color=R+100+G+200+B+150");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect(result1).toBe("color=R%20100%20G%20200%20B%20150");

});
});

Expand All @@ -113,7 +113,7 @@ describe("QueryParameter - style:pipeDelimited", () => {
style: "pipeDelimited",
explode: false,
});
expect(result1).toBe("blue|black|brown");
expect(result1).toBe("color=blue%7Cblack%7Cbrown");
});
test("explode:false value:object", () => {
const result1 = QueryParameter.generate("color", {
Expand All @@ -125,7 +125,7 @@ describe("QueryParameter - style:pipeDelimited", () => {
style: "pipeDelimited",
explode: false,
});
expect(result1).toBe("R|100|G|200|B|150");
expect(result1).toBe("color=R%7C100%7CG%7C200%7CB%7C150");
});
});

Expand All @@ -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).toBe("color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150");
});
});