Skip to content

Commit 39e8def

Browse files
committed
feat: add parameter serialization option to factory method
1 parent 1102151 commit 39e8def

File tree

3 files changed

+51
-17
lines changed

3 files changed

+51
-17
lines changed

src/openapi-typescript/fetch-factory.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ function fetchFactory<Paths>(options?: InitParameters) {
1616
const basePath = options?.baseUrl ?? "";
1717
const defaultInit = options?.defaultInit ?? {};
1818
const fetchMethod = options?.fetchMethod ?? fetch;
19+
const serialization = options?.parameterSerialization;
1920

2021
async function fetcher<
2122
Path extends keyof Paths,
@@ -25,7 +26,10 @@ function fetchFactory<Paths>(options?: InitParameters) {
2526
const options = init as unknown as { method: Method } & AllFetchOptions;
2627

2728
const path = getPath(input as string, options.parameters?.path);
28-
const query = getQuery(options.parameters?.query);
29+
const query = getQuery(
30+
options.parameters?.query ?? null,
31+
serialization?.explode
32+
);
2933
const url = basePath + path + query;
3034

3135
const fetchInit = buildInit(defaultInit, options);
@@ -66,17 +70,26 @@ function getPath(path: string, pathParams?: Record<string, string | number>) {
6670
}
6771

6872
function getQuery(
69-
params?: Record<string, string | number | string[] | number[]>
73+
params: Record<string, string | number | string[] | number[]> | null,
74+
explode?: boolean
7075
): string {
7176
if (!params) {
7277
return "";
7378
}
7479

7580
const searchParams = Object.entries(params).map(([key, value]) => {
7681
if (Array.isArray(value)) {
77-
return value
78-
.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(`${v}`)}`)
79-
.join("&");
82+
if (explode) {
83+
return value
84+
.map(
85+
(v) => `${encodeURIComponent(key)}=${encodeURIComponent(`${v}`)}`
86+
)
87+
.join("&");
88+
} else {
89+
return `${encodeURIComponent(key)}=${value
90+
.map((v) => encodeURIComponent(`${v}`))
91+
.join(",")}`;
92+
}
8093
}
8194
return `${encodeURIComponent(key)}=${encodeURIComponent(`${params[key]}`)}`;
8295
});

src/types/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type InitParameters = {
22
baseUrl?: string;
33
defaultInit?: Omit<RequestInit, "method">;
44
fetchMethod?: typeof fetch;
5+
parameterSerialization?: { explode?: boolean };
56
};
67

78
export type HttpMethod =

test/openapi-typescript/fetch-factory.test.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,6 @@ describe("Generated fetch request", () => {
197197
);
198198
});
199199

200-
it("resolves array query parameter", () => {
201-
customFetch("/pet/findByStatus", {
202-
method: "get",
203-
// @ts-ignore
204-
parameters: { query: { status: ["available", "free"] } },
205-
});
206-
207-
expect((mockedFetch.mock.calls[0] as any)[0]).toBe(
208-
"https://petstore3.swagger.io/pet/findByStatus?status=available&status=free"
209-
);
210-
});
211-
212200
it("merges headers with default headers", () => {
213201
customFetch("/store/inventory", {
214202
method: "get",
@@ -284,6 +272,38 @@ describe("Generated fetch request", () => {
284272
});
285273
});
286274

275+
describe("Handles parameter serialization", () => {
276+
const queryFetch = FetchFactory.build<paths>({ fetchMethod: mockedFetch });
277+
const explodeQueryFetch = FetchFactory.build<paths>({
278+
parameterSerialization: { explode: true },
279+
fetchMethod: mockedFetch,
280+
});
281+
282+
it("resolves query array as comma separated when default", () => {
283+
queryFetch("/pet/findByStatus", {
284+
method: "get",
285+
// @ts-ignore
286+
parameters: { query: { status: ["available", "free"] } },
287+
});
288+
289+
expect((mockedFetch.mock.calls[0] as any)[0]).toBe(
290+
"/pet/findByStatus?status=available,free"
291+
);
292+
});
293+
294+
it("resolves query array as comma separated when set to explode", () => {
295+
explodeQueryFetch("/pet/findByStatus", {
296+
method: "get",
297+
// @ts-ignore
298+
parameters: { query: { status: ["available", "free"] } },
299+
});
300+
301+
expect((mockedFetch.mock.calls[0] as any)[0]).toBe(
302+
"/pet/findByStatus?status=available&status=free"
303+
);
304+
});
305+
});
306+
287307
describe("Generated fetch response", () => {
288308
it("calls json() of response", async () => {
289309
const response = await customFetch("/store/inventory", { method: "get" });

0 commit comments

Comments
 (0)