diff --git a/.gitignore b/.gitignore index a6c68cb03..322351819 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,5 @@ typings/ package-lock.json tests/generated/** -.turbo \ No newline at end of file +.turbo +.next diff --git a/packages/fetch/src/index.ts b/packages/fetch/src/index.ts index 33bf089d5..cbc165775 100644 --- a/packages/fetch/src/index.ts +++ b/packages/fetch/src/index.ts @@ -11,7 +11,13 @@ import { toObjectString, generateBodyOptions, isObject, + resolveRef, } from '@orval/core'; +import { + PathItemObject, + ParameterObject, + ReferenceObject, +} from 'openapi3-ts/oas30'; export const generateRequestFunction = ( { @@ -26,7 +32,7 @@ export const generateRequestFunction = ( formUrlEncoded, override, }: GeneratorVerbOptions, - { route }: GeneratorOptions, + { route, context, pathRoute }: GeneratorOptions, ) => { const isRequestOptions = override?.requestOptions !== false; const isFormData = override?.formData !== false; @@ -42,18 +48,51 @@ export const generateRequestFunction = ( ), 'implementation', ); + + const spec = context.specs[context.specKey].paths[pathRoute] as + | PathItemObject + | undefined; + const parameters = + spec?.[verb]?.parameters || ([] as (ParameterObject | ReferenceObject)[]); + + const explodeParameters = parameters.filter((parameter) => { + const { schema } = resolveRef(parameter, context); + + return schema.in === 'query' && schema.explode; + }); + + const explodeParametersNames = explodeParameters.map((parameter) => { + const { schema } = resolveRef(parameter, context); + + return schema.name; + }); + + const explodeArrayImplementation = + explodeParameters.length > 0 + ? `const explodeParameters = ${JSON.stringify(explodeParametersNames)}; + + if (value instanceof Array && explodeParameters.includes(key)) { + value.forEach((v) => normalizedParams.append(key, v === null ? 'null' : v.toString())); + return; + } + ` + : ''; + + const isExplodeParametersOnly = + explodeParameters.length === parameters.length; + + const nomalParamsImplementation = `if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + }`; + const getUrlFnImplementation = `export const ${getUrlFnName} = (${getUrlFnProps}) => { ${ queryParams - ? ` - const normalizedParams = new URLSearchParams(); + ? ` const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); - } + ${explodeArrayImplementation} + ${!isExplodeParametersOnly ? nomalParamsImplementation : ''} });` : '' } diff --git a/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.msw.ts b/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.msw.ts index 27ae3c3fa..6d152dbe1 100644 --- a/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.msw.ts +++ b/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.msw.ts @@ -23,6 +23,7 @@ export const getCreatePetsResponseMock = ( ): Pet => ({ id: faker.number.int({ min: undefined, max: undefined }), name: faker.word.sample(), + tag: faker.word.sample(), ...overrideResponse, }); @@ -31,6 +32,7 @@ export const getUpdatePetsResponseMock = ( ): Pet => ({ id: faker.number.int({ min: undefined, max: undefined }), name: faker.word.sample(), + tag: faker.word.sample(), ...overrideResponse, }); @@ -39,6 +41,7 @@ export const getShowPetByIdResponseMock = ( ): Pet => ({ id: faker.number.int({ min: undefined, max: undefined }), name: faker.word.sample(), + tag: faker.word.sample(), ...overrideResponse, }); diff --git a/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.ts b/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.ts index 04ba03b68..191dda872 100644 --- a/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.ts +++ b/samples/hono/hono-with-fetch-client/next-app/app/gen/pets/pets.ts @@ -23,10 +23,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/hono/hono-with-fetch-client/next-app/next-env.d.ts b/samples/hono/hono-with-fetch-client/next-app/next-env.d.ts new file mode 100644 index 000000000..4f11a03dc --- /dev/null +++ b/samples/hono/hono-with-fetch-client/next-app/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/samples/next-app-with-fetch/app/gen/pets/pets.ts b/samples/next-app-with-fetch/app/gen/pets/pets.ts index 46acfd228..4e197ed9a 100644 --- a/samples/next-app-with-fetch/app/gen/pets/pets.ts +++ b/samples/next-app-with-fetch/app/gen/pets/pets.ts @@ -52,10 +52,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/react-app-with-swr/fetch-client/src/api/endpoints/swaggerPetstore.ts b/samples/react-app-with-swr/fetch-client/src/api/endpoints/swaggerPetstore.ts index 9a69a903c..06afdcd5f 100644 --- a/samples/react-app-with-swr/fetch-client/src/api/endpoints/swaggerPetstore.ts +++ b/samples/react-app-with-swr/fetch-client/src/api/endpoints/swaggerPetstore.ts @@ -23,10 +23,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/react-query/custom-fetch/src/gen/pets/pets.ts b/samples/react-query/custom-fetch/src/gen/pets/pets.ts index 5fe866b9f..6a6b6635e 100644 --- a/samples/react-query/custom-fetch/src/gen/pets/pets.ts +++ b/samples/react-query/custom-fetch/src/gen/pets/pets.ts @@ -68,10 +68,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/svelte-query/custom-fetch/src/gen/pets/pets.ts b/samples/svelte-query/custom-fetch/src/gen/pets/pets.ts index 796f85137..05180ca29 100644 --- a/samples/svelte-query/custom-fetch/src/gen/pets/pets.ts +++ b/samples/svelte-query/custom-fetch/src/gen/pets/pets.ts @@ -65,10 +65,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/swr-with-zod/src/gen/endpoints/pets/pets.ts b/samples/swr-with-zod/src/gen/endpoints/pets/pets.ts index 2f40d1b45..3367a76b0 100644 --- a/samples/swr-with-zod/src/gen/endpoints/pets/pets.ts +++ b/samples/swr-with-zod/src/gen/endpoints/pets/pets.ts @@ -56,10 +56,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/samples/vue-query/custom-fetch/src/gen/pets/pets.ts b/samples/vue-query/custom-fetch/src/gen/pets/pets.ts index 065503534..35e6ef48f 100644 --- a/samples/vue-query/custom-fetch/src/gen/pets/pets.ts +++ b/samples/vue-query/custom-fetch/src/gen/pets/pets.ts @@ -67,10 +67,8 @@ export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { - if (value === null) { - normalizedParams.append(key, 'null'); - } else if (value !== undefined) { - normalizedParams.append(key, value.toString()); + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); } }); diff --git a/tests/configs/fetch.config.ts b/tests/configs/fetch.config.ts index 810bccc97..ec8e88631 100644 --- a/tests/configs/fetch.config.ts +++ b/tests/configs/fetch.config.ts @@ -168,4 +168,14 @@ export default defineConfig({ target: '../specifications/form-url-encoded.yaml', }, }, + parameters: { + output: { + target: '../generated/fetch/parameters/endpoints.ts', + schemas: '../generated/fetch/parameters/model', + client: 'fetch', + }, + input: { + target: '../specifications/parameters.yaml', + }, + }, }); diff --git a/tests/specifications/parameters.yaml b/tests/specifications/parameters.yaml index 0f82dff00..ba70bc2ee 100644 --- a/tests/specifications/parameters.yaml +++ b/tests/specifications/parameters.yaml @@ -71,6 +71,15 @@ paths: required: false schema: type: boolean + - name: tag + in: query + description: include pets tag + required: false + explode: true + schema: + type: array + items: + type: string responses: '200': description: A paged array of pets