From ea695cd38e37ddc726834900c7e7e22a355234a5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 24 Jun 2023 16:59:55 +1000 Subject: [PATCH] feat(openapi-fetch): add global `querySerializer` option (#1182) --- .changeset/four-carpets-push.md | 5 ++++ packages/openapi-fetch/README.md | 29 ++++++++++++++++++++++ packages/openapi-fetch/src/index.test.ts | 31 ++++++++++++++++++++++++ packages/openapi-fetch/src/index.ts | 6 +++-- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .changeset/four-carpets-push.md diff --git a/.changeset/four-carpets-push.md b/.changeset/four-carpets-push.md new file mode 100644 index 000000000..d5d7cfaf5 --- /dev/null +++ b/.changeset/four-carpets-push.md @@ -0,0 +1,5 @@ +--- +"openapi-fetch": minor +--- + +Add global `querySerializer()` option to `createClient()` diff --git a/packages/openapi-fetch/README.md b/packages/openapi-fetch/README.md index 345bc4ee2..a90dce5d8 100644 --- a/packages/openapi-fetch/README.md +++ b/packages/openapi-fetch/README.md @@ -188,6 +188,35 @@ Note that this happens **at the request level** so that you still get correct ty _Thanks, [@ezpuzz](https://github.com/ezpuzz)!_ +Provide a `querySerializer()` to `createClient()` to globally override the default `URLSearchParams` serializer. Serializers provided to a specific request method still override the global default. + +```ts +import createClient, { defaultSerializer } from "openapi-fetch"; +import { paths } from "./v1"; // generated from openapi-typescript +import { queryString } from "query-string"; + +const { get, post } = createClient({ + baseUrl: "https://myapi.dev/v1/", + querySerializer: (q) => queryString.stringify(q, { arrayFormat: "none" }), // Override the default `URLSearchParams` serializer +}); + +const { data, error } = await get("/posts/", { + params: { + query: { categories: ["dogs", "cats", "lizards"] }, // Use the serializer specified in `createClient()` + }, +}); + +const { data, error } = await get("/images/{image_id}", { + params: { + path: { image_id: "image-id" }, + query: { size: 512 }, + }, + querySerializer: defaultSerializer, // Use `openapi-fetch`'s `URLSearchParams` serializer +}); +``` + +_Thanks, [@psychedelicious](https://github.com/psychedelicious)!_ + ## Examples ### 🔒 Handling Auth diff --git a/packages/openapi-fetch/src/index.test.ts b/packages/openapi-fetch/src/index.test.ts index 640ed9318..1935a63bf 100644 --- a/packages/openapi-fetch/src/index.test.ts +++ b/packages/openapi-fetch/src/index.test.ts @@ -387,6 +387,37 @@ describe("client", () => { expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json"); }); + + it("applies global serializer", async () => { + const client = createClient({ + querySerializer: (q) => `alpha=${q.version}&beta=${q.format}`, + }); + mockFetchOnce({ status: 200, body: "{}" }); + await client.get("/blogposts/{post_id}", { + params: { + path: { post_id: "my-post" }, + query: { version: 2, format: "json" }, + }, + }); + + expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json"); + }); + + it("overrides global serializer if provided", async () => { + const client = createClient({ + querySerializer: () => "query", + }); + mockFetchOnce({ status: 200, body: "{}" }); + await client.get("/blogposts/{post_id}", { + params: { + path: { post_id: "my-post" }, + query: { version: 2, format: "json" }, + }, + querySerializer: (q) => `alpha=${q.version}&beta=${q.format}`, + }); + + expect(fetchMocker.mock.calls[0][0]).toBe("/blogposts/my-post?alpha=2&beta=json"); + }); }); }); diff --git a/packages/openapi-fetch/src/index.ts b/packages/openapi-fetch/src/index.ts index 7d2b1a0ab..54fff0ee1 100644 --- a/packages/openapi-fetch/src/index.ts +++ b/packages/openapi-fetch/src/index.ts @@ -10,6 +10,8 @@ interface ClientOptions extends RequestInit { baseUrl?: string; /** custom fetch (defaults to globalThis.fetch) */ fetch?: typeof fetch; + /** global querySerializer */ + querySerializer?: QuerySerializer; } export interface BaseParams { params?: { query?: Record }; @@ -82,7 +84,7 @@ export function createFinalURL(url: string, options: { baseUrl?: string; para } export default function createClient(clientOptions: ClientOptions = {}) { - const { fetch = globalThis.fetch, ...options } = clientOptions; + const { fetch = globalThis.fetch, querySerializer: globalQuerySerializer, ...options } = clientOptions; const defaultHeaders = new Headers({ ...DEFAULT_HEADERS, @@ -90,7 +92,7 @@ export default function createClient(clientOptions: ClientOpti }); async function coreFetch

(url: P, fetchOptions: FetchOptions): Promise> { - const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = defaultSerializer, ...init } = fetchOptions || {}; + const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = globalQuerySerializer ?? defaultSerializer, ...init } = fetchOptions || {}; // URL const finalURL = createFinalURL(url as string, { baseUrl: options.baseUrl, params, querySerializer });