Skip to content

Commit 431a98f

Browse files
feat(openapi-fetch): add global querySerializer option (#1182) (#1183)
1 parent 203cb66 commit 431a98f

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

.changeset/four-carpets-push.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": minor
3+
---
4+
5+
Add global `querySerializer()` option to `createClient()`

packages/openapi-fetch/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,35 @@ Note that this happens **at the request level** so that you still get correct ty
188188

189189
_Thanks, [@ezpuzz](https://github.com/ezpuzz)!_
190190

191+
Provide a `querySerializer()` to `createClient()` to globally override the default `URLSearchParams` serializer. Serializers provided to a specific request method still override the global default.
192+
193+
```ts
194+
import createClient, { defaultSerializer } from "openapi-fetch";
195+
import { paths } from "./v1"; // generated from openapi-typescript
196+
import { queryString } from "query-string";
197+
198+
const { get, post } = createClient<paths>({
199+
baseUrl: "https://myapi.dev/v1/",
200+
querySerializer: (q) => queryString.stringify(q, { arrayFormat: "none" }), // Override the default `URLSearchParams` serializer
201+
});
202+
203+
const { data, error } = await get("/posts/", {
204+
params: {
205+
query: { categories: ["dogs", "cats", "lizards"] }, // Use the serializer specified in `createClient()`
206+
},
207+
});
208+
209+
const { data, error } = await get("/images/{image_id}", {
210+
params: {
211+
path: { image_id: "image-id" },
212+
query: { size: 512 },
213+
},
214+
querySerializer: defaultSerializer, // Use `openapi-fetch`'s `URLSearchParams` serializer
215+
});
216+
```
217+
218+
_Thanks, [@psychedelicious](https://github.com/psychedelicious)!_
219+
191220
## Examples
192221

193222
### 🔒 Handling Auth

packages/openapi-fetch/src/index.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,37 @@ describe("client", () => {
387387

388388
expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json");
389389
});
390+
391+
it("applies global serializer", async () => {
392+
const client = createClient<paths>({
393+
querySerializer: (q) => `alpha=${q.version}&beta=${q.format}`,
394+
});
395+
mockFetchOnce({ status: 200, body: "{}" });
396+
await client.get("/blogposts/{post_id}", {
397+
params: {
398+
path: { post_id: "my-post" },
399+
query: { version: 2, format: "json" },
400+
},
401+
});
402+
403+
expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json");
404+
});
405+
406+
it("overrides global serializer if provided", async () => {
407+
const client = createClient<paths>({
408+
querySerializer: () => "query",
409+
});
410+
mockFetchOnce({ status: 200, body: "{}" });
411+
await client.get("/blogposts/{post_id}", {
412+
params: {
413+
path: { post_id: "my-post" },
414+
query: { version: 2, format: "json" },
415+
},
416+
querySerializer: (q) => `alpha=${q.version}&beta=${q.format}`,
417+
});
418+
419+
expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json");
420+
});
390421
});
391422
});
392423

packages/openapi-fetch/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ interface ClientOptions extends RequestInit {
1010
baseUrl?: string;
1111
/** custom fetch (defaults to globalThis.fetch) */
1212
fetch?: typeof fetch;
13+
/** global querySerializer */
14+
querySerializer?: QuerySerializer<unknown>;
1315
}
1416
export interface BaseParams {
1517
params?: { query?: Record<string, unknown> };
@@ -82,15 +84,15 @@ export function createFinalURL<O>(url: string, options: { baseUrl?: string; para
8284
}
8385

8486
export default function createClient<Paths extends {}>(clientOptions: ClientOptions = {}) {
85-
const { fetch = globalThis.fetch, ...options } = clientOptions;
87+
const { fetch = globalThis.fetch, querySerializer: globalQuerySerializer, ...options } = clientOptions;
8688

8789
const defaultHeaders = new Headers({
8890
...DEFAULT_HEADERS,
8991
...(options.headers ?? {}),
9092
});
9193

9294
async function coreFetch<P extends keyof Paths, M extends HttpMethod>(url: P, fetchOptions: FetchOptions<M extends keyof Paths[P] ? Paths[P][M] : never>): Promise<FetchResponse<M extends keyof Paths[P] ? Paths[P][M] : unknown>> {
93-
const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = defaultSerializer, ...init } = fetchOptions || {};
95+
const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = globalQuerySerializer ?? defaultSerializer, ...init } = fetchOptions || {};
9496

9597
// URL
9698
const finalURL = createFinalURL(url as string, { baseUrl: options.baseUrl, params, querySerializer });

0 commit comments

Comments
 (0)