From cf1f4b51f422f33a90b23792f2d59c74ec8e4dbe Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Wed, 8 Jan 2025 09:27:17 +0100 Subject: [PATCH] fix(fetch): take param serializer setting into account --- .../pages/reference/configuration/output.md | 7 ++- packages/fetch/src/index.ts | 56 ++++++++++++++---- tests/configs/default.config.ts | 31 ++++++++++ tests/configs/fetch.config.ts | 33 +++++++++++ tests/mutators/params-serializer.ts | 11 ++++ yarn.lock | 58 +++++++++---------- 6 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 tests/mutators/params-serializer.ts diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index e30b8d8c1..5613178d7 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -1726,6 +1726,8 @@ Use this property to add a custom params serializer to all requests that use que If you provide an object you can also add a default property to use an export default function. +If this is not specified, params are serialized as per `axios` default when using `axios`, or by using `URLSearchParams` when using `fetch`. + Example: ```js @@ -1759,9 +1761,10 @@ export const customParamsSerializerFn = ( Type: `Object` -Use this property to add a default params serializer. Current options are: `qs`. +Use this property to decide how params are serialized. This is only taken into account when `paramsSerializer` is not defined. +Currently, only `qs` is the available option. Read more about `qs` and it's settings [here](https://www.npmjs.com/package/qs). -All options are then passed to the chosen serializer. +If this is not specified, params are serialized as per `axios` default when using `axios`, or by using `URLSearchParams` when using `fetch`. Example: diff --git a/packages/fetch/src/index.ts b/packages/fetch/src/index.ts index 1f2f331d1..271199833 100644 --- a/packages/fetch/src/index.ts +++ b/packages/fetch/src/index.ts @@ -12,6 +12,8 @@ import { generateBodyOptions, isObject, resolveRef, + GeneratorDependency, + ClientDependenciesBuilder, } from '@orval/core'; import { PathItemObject, @@ -86,23 +88,30 @@ export const generateRequestFunction = ( normalizedParams.append(key, value === null ? 'null' : value.toString()) }`; - const getUrlFnImplementation = `export const ${getUrlFnName} = (${getUrlFnProps}) => { -${ - queryParams - ? ` const normalizedParams = new URLSearchParams(); + const queryImplementation = queryParams + ? override.paramsSerializer + ? `const normalizedParams = ${override.paramsSerializer.name}(params);` + : override.paramsSerializerOptions?.qs + ? `const normalizedParams = qs.stringify(params, ${JSON.stringify( + override.paramsSerializerOptions!.qs, + )});` + : `const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { ${explodeArrayImplementation} ${!isExplodeParametersOnly ? nomalParamsImplementation : ''} });` - : '' -} + : ''; + const returnQueryImplementation = queryParams + ? override.paramsSerializer || override.paramsSerializerOptions?.qs + ? `return normalizedParams ? \`${route}${'?${normalizedParams}'}\` : \`${route}\`` + : `return normalizedParams.size ? \`${route}${'?${normalizedParams.toString()}'}\` : \`${route}\`` + : `return \`${route}\``; - ${ - queryParams - ? `return normalizedParams.size ? \`${route}${'?${normalizedParams.toString()}'}\` : \`${route}\`` - : `return \`${route}\`` - } + const getUrlFnImplementation = `export const ${getUrlFnName} = (${getUrlFnProps}) => { + ${queryImplementation} + + ${returnQueryImplementation} }\n`; const isNdJson = response.contentTypes.some( @@ -235,9 +244,32 @@ export const generateClient: ClientBuilder = (verbOptions, options) => { }; }; +const PARAMS_SERIALIZER_DEPENDENCIES: GeneratorDependency[] = [ + { + exports: [ + { + name: 'qs', + default: true, + values: true, + syntheticDefaultImport: true, + }, + ], + dependency: 'qs', + }, +]; + +const getFetchDependencies: ClientDependenciesBuilder = ( + _: boolean, + hasParamsSerializerOptions: boolean, +) => { + return [ + ...(hasParamsSerializerOptions ? PARAMS_SERIALIZER_DEPENDENCIES : []), + ]; +}; + const fetchClientBuilder: ClientGeneratorsBuilder = { client: generateClient, - dependencies: () => [], + dependencies: getFetchDependencies, }; export const builder = () => () => fetchClientBuilder; diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index d233eda9f..aed97c011 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -209,4 +209,35 @@ export default defineConfig({ target: '../specifications/petstore.yaml', }, }, + paramsSerializer: { + output: { + target: '../generated/default/params-serializer/endpoints.ts', + schemas: '../generated/default/params-serializer/model', + override: { + paramsSerializer: { + path: '../mutators/params-serializer.ts', + name: 'customParamsSerializer', + }, + }, + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, + paramsSerializerOptions: { + output: { + target: '../generated/default/params-serializer-options/endpoints.ts', + schemas: '../generated/default/params-serializer-options/model', + override: { + paramsSerializerOptions: { + qs: { + arrayFormat: 'repeat', + }, + }, + }, + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, }); diff --git a/tests/configs/fetch.config.ts b/tests/configs/fetch.config.ts index 2494e1887..481ccb32f 100644 --- a/tests/configs/fetch.config.ts +++ b/tests/configs/fetch.config.ts @@ -189,4 +189,37 @@ export default defineConfig({ target: '../specifications/parameters.yaml', }, }, + paramsSerializer: { + output: { + target: '../generated/fetch/params-serializer/endpoints.ts', + schemas: '../generated/fetch/params-serializer/model', + client: 'fetch', + override: { + paramsSerializer: { + path: '../mutators/params-serializer.ts', + name: 'customParamsSerializer', + }, + }, + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, + paramsSerializerOptions: { + output: { + target: '../generated/fetch/params-serializer-options/endpoints.ts', + schemas: '../generated/fetch/params-serializer-options/model', + client: 'fetch', + override: { + paramsSerializerOptions: { + qs: { + arrayFormat: 'repeat', + }, + }, + }, + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, }); diff --git a/tests/mutators/params-serializer.ts b/tests/mutators/params-serializer.ts new file mode 100644 index 000000000..c233f3a9a --- /dev/null +++ b/tests/mutators/params-serializer.ts @@ -0,0 +1,11 @@ +export const customParamsSerializer = (params: Record): string => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); + } + }); + + return normalizedParams.toString(); +}; diff --git a/yarn.lock b/yarn.lock index fea96d833..767babbbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1253,23 +1253,23 @@ __metadata: languageName: node linkType: hard -"@orval/angular@npm:7.3.0, @orval/angular@workspace:packages/angular": +"@orval/angular@npm:7.4.0, @orval/angular@workspace:packages/angular": version: 0.0.0-use.local resolution: "@orval/angular@workspace:packages/angular" dependencies: - "@orval/core": "npm:7.3.0" + "@orval/core": "npm:7.4.0" languageName: unknown linkType: soft -"@orval/axios@npm:7.3.0, @orval/axios@workspace:packages/axios": +"@orval/axios@npm:7.4.0, @orval/axios@workspace:packages/axios": version: 0.0.0-use.local resolution: "@orval/axios@workspace:packages/axios" dependencies: - "@orval/core": "npm:7.3.0" + "@orval/core": "npm:7.4.0" languageName: unknown linkType: soft -"@orval/core@npm:7.3.0, @orval/core@workspace:packages/core": +"@orval/core@npm:7.4.0, @orval/core@workspace:packages/core": version: 0.0.0-use.local resolution: "@orval/core@workspace:packages/core" dependencies: @@ -1308,62 +1308,62 @@ __metadata: languageName: unknown linkType: soft -"@orval/fetch@npm:7.3.0, @orval/fetch@workspace:packages/fetch": +"@orval/fetch@npm:7.4.0, @orval/fetch@workspace:packages/fetch": version: 0.0.0-use.local resolution: "@orval/fetch@workspace:packages/fetch" dependencies: - "@orval/core": "npm:7.3.0" + "@orval/core": "npm:7.4.0" languageName: unknown linkType: soft -"@orval/hono@npm:7.3.0, @orval/hono@workspace:packages/hono": +"@orval/hono@npm:7.4.0, @orval/hono@workspace:packages/hono": version: 0.0.0-use.local resolution: "@orval/hono@workspace:packages/hono" dependencies: - "@orval/core": "npm:7.3.0" - "@orval/zod": "npm:7.3.0" + "@orval/core": "npm:7.4.0" + "@orval/zod": "npm:7.4.0" "@types/lodash.uniq": "npm:^4.5.7" lodash.uniq: "npm:^4.5.0" languageName: unknown linkType: soft -"@orval/mock@npm:7.3.0, @orval/mock@workspace:packages/mock": +"@orval/mock@npm:7.4.0, @orval/mock@workspace:packages/mock": version: 0.0.0-use.local resolution: "@orval/mock@workspace:packages/mock" dependencies: - "@orval/core": "npm:7.3.0" + "@orval/core": "npm:7.4.0" lodash.get: "npm:^4.4.2" lodash.omit: "npm:^4.5.0" openapi3-ts: "npm:^4.2.2" languageName: unknown linkType: soft -"@orval/query@npm:7.3.0, @orval/query@workspace:packages/query": +"@orval/query@npm:7.4.0, @orval/query@workspace:packages/query": version: 0.0.0-use.local resolution: "@orval/query@workspace:packages/query" dependencies: - "@orval/core": "npm:7.3.0" - "@orval/fetch": "npm:7.3.0" + "@orval/core": "npm:7.4.0" + "@orval/fetch": "npm:7.4.0" "@types/lodash.omitby": "npm:^4.6.7" lodash.omitby: "npm:^4.6.0" vitest: "npm:^0.34.6" languageName: unknown linkType: soft -"@orval/swr@npm:7.3.0, @orval/swr@workspace:packages/swr": +"@orval/swr@npm:7.4.0, @orval/swr@workspace:packages/swr": version: 0.0.0-use.local resolution: "@orval/swr@workspace:packages/swr" dependencies: - "@orval/core": "npm:7.3.0" - "@orval/fetch": "npm:7.3.0" + "@orval/core": "npm:7.4.0" + "@orval/fetch": "npm:7.4.0" languageName: unknown linkType: soft -"@orval/zod@npm:7.3.0, @orval/zod@workspace:packages/zod": +"@orval/zod@npm:7.4.0, @orval/zod@workspace:packages/zod": version: 0.0.0-use.local resolution: "@orval/zod@workspace:packages/zod" dependencies: - "@orval/core": "npm:7.3.0" + "@orval/core": "npm:7.4.0" "@types/lodash.uniq": "npm:^4.5.7" lodash.uniq: "npm:^4.5.0" languageName: unknown @@ -7578,15 +7578,15 @@ __metadata: resolution: "orval@workspace:packages/orval" dependencies: "@apidevtools/swagger-parser": "npm:^10.1.0" - "@orval/angular": "npm:7.3.0" - "@orval/axios": "npm:7.3.0" - "@orval/core": "npm:7.3.0" - "@orval/fetch": "npm:7.3.0" - "@orval/hono": "npm:7.3.0" - "@orval/mock": "npm:7.3.0" - "@orval/query": "npm:7.3.0" - "@orval/swr": "npm:7.3.0" - "@orval/zod": "npm:7.3.0" + "@orval/angular": "npm:7.4.0" + "@orval/axios": "npm:7.4.0" + "@orval/core": "npm:7.4.0" + "@orval/fetch": "npm:7.4.0" + "@orval/hono": "npm:7.4.0" + "@orval/mock": "npm:7.4.0" + "@orval/query": "npm:7.4.0" + "@orval/swr": "npm:7.4.0" + "@orval/zod": "npm:7.4.0" "@types/inquirer": "npm:^9.0.6" "@types/js-yaml": "npm:^4.0.8" "@types/lodash.uniq": "npm:^4.5.8"