diff --git a/packages/rtk-query-codegen-openapi/src/generate.ts b/packages/rtk-query-codegen-openapi/src/generate.ts index b62d987cb2..d128d100b4 100644 --- a/packages/rtk-query-codegen-openapi/src/generate.ts +++ b/packages/rtk-query-codegen-openapi/src/generate.ts @@ -110,7 +110,8 @@ export async function generateApi( filterEndpoints, endpointOverrides, unionUndefined, - encodeParams = false, + encodePathParams = false, + encodeQueryParams = false, flattenArg = false, includeDefault = false, useEnumType = false, @@ -398,7 +399,14 @@ export async function generateApi( type: isQuery ? 'query' : 'mutation', Response: ResponseTypeName, QueryArg, - queryFn: generateQueryFn({ operationDefinition, queryArg, isQuery, isFlatArg, encodeParams }), + queryFn: generateQueryFn({ + operationDefinition, + queryArg, + isQuery, + isFlatArg, + encodePathParams, + encodeQueryParams, + }), extraEndpointsProps: isQuery ? generateQueryEndpointProps({ operationDefinition }) : generateMutationEndpointProps({ operationDefinition }), @@ -411,13 +419,15 @@ export async function generateApi( queryArg, isFlatArg, isQuery, - encodeParams, + encodePathParams, + encodeQueryParams, }: { operationDefinition: OperationDefinition; queryArg: QueryArgDefinitions; isFlatArg: boolean; isQuery: boolean; - encodeParams: boolean; + encodePathParams: boolean; + encodeQueryParams: boolean; }) { const { path, verb } = operationDefinition; @@ -434,14 +444,21 @@ export async function generateApi( const properties = parameters.map((param) => { const value = isFlatArg ? rootObject : accessProperty(rootObject, param.name); - return createPropertyAssignment( - param.originalName, - encodeParams && param.param?.in === 'query' - ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [ - factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]), - ]) - : value - ); + + const encodedValue = + encodeQueryParams && param.param?.in === 'query' + ? factory.createConditionalExpression( + value, + undefined, + factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [ + factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]), + ]), + undefined, + factory.createIdentifier('undefined') + ) + : value; + + return createPropertyAssignment(param.originalName, encodedValue); }); return factory.createPropertyAssignment( @@ -463,7 +480,7 @@ export async function generateApi( [ factory.createPropertyAssignment( factory.createIdentifier('url'), - generatePathExpression(path, pickParams('path'), rootObject, isFlatArg, encodeParams) + generatePathExpression(path, pickParams('path'), rootObject, isFlatArg, encodePathParams) ), isQuery && verb.toUpperCase() === 'GET' ? undefined @@ -511,7 +528,7 @@ function generatePathExpression( pathParameters: QueryArgDefinition[], rootObject: ts.Identifier, isFlatArg: boolean, - encodeParams: boolean + encodePathParams: boolean ) { const expressions: Array<[string, string]> = []; @@ -529,7 +546,7 @@ function generatePathExpression( factory.createTemplateHead(head), expressions.map(([prop, literal], index) => { const value = isFlatArg ? rootObject : accessProperty(rootObject, prop); - const encodedValue = encodeParams + const encodedValue = encodePathParams ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [ factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]), ]) diff --git a/packages/rtk-query-codegen-openapi/src/types.ts b/packages/rtk-query-codegen-openapi/src/types.ts index b5c42561c2..437e058087 100644 --- a/packages/rtk-query-codegen-openapi/src/types.ts +++ b/packages/rtk-query-codegen-openapi/src/types.ts @@ -75,9 +75,14 @@ export interface CommonOptions { tag?: boolean; /** * defaults to false - * `true` will add `encodeURIComponent` to the generated query params + * `true` will add `encodeURIComponent` to the generated path parameters */ - encodeParams?: boolean; + encodePathParams?: boolean; + /** + * defaults to false + * `true` will add `encodeURIComponent` to the generated query parameters + */ + encodeQueryParams?: boolean; /** * defaults to false * `true` will "flatten" the arg so that you can do things like `useGetEntityById(1)` instead of `useGetEntityById({ entityId: 1 })` diff --git a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts index 3c1c9df935..93ed235849 100644 --- a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts +++ b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts @@ -174,28 +174,28 @@ describe('endpoint overrides', () => { }); }); -describe('option encodeParams', () => { +describe('option encodePathParams', () => { const config = { apiFile: './fixtures/emptyApi.ts', schemaFile: resolve(__dirname, 'fixtures/petstore.json'), - encodeParams: true, + encodePathParams: true, }; - it('should encode query parameters', async () => { + it('should encode path parameters', async () => { const api = await generateEndpoints({ ...config, - filterEndpoints: ['findPetsByStatus'], + filterEndpoints: ['getOrderById'], }); - expect(api).toContain('status: encodeURIComponent(String(queryArg.status))'); + // eslint-disable-next-line no-template-curly-in-string + expect(api).toContain('`/store/order/${encodeURIComponent(String(queryArg.orderId))}`'); }); - it('should encode path parameters', async () => { + it('should not encode query parameters', async () => { const api = await generateEndpoints({ ...config, - filterEndpoints: ['getOrderById'], + filterEndpoints: ['findPetsByStatus'], }); - // eslint-disable-next-line no-template-curly-in-string - expect(api).toContain('`/store/order/${encodeURIComponent(String(queryArg.orderId))}`'); + expect(api).toContain('status: queryArg.status'); }); it('should not encode body parameters', async () => { @@ -217,18 +217,63 @@ describe('option encodeParams', () => { expect(api).toContain('`/store/order/${encodeURIComponent(String(queryArg))}`'); }); - it('should not encode parameters when encodeParams is false', async () => { + it('should not encode path parameters when encodePathParams is false', async () => { const api = await generateEndpoints({ ...config, - encodeParams: false, + encodePathParams: false, filterEndpoints: ['findPetsByStatus', 'getOrderById'], }); - expect(api).toContain('status: queryArg.status'); // eslint-disable-next-line no-template-curly-in-string expect(api).toContain('`/store/order/${queryArg.orderId}`'); }); }); +describe('option encodeQueryParams', () => { + const config = { + apiFile: './fixtures/emptyApi.ts', + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + encodeQueryParams: true, + }; + + it('should conditionally encode query parameters', async () => { + const api = await generateEndpoints({ + ...config, + filterEndpoints: ['findPetsByStatus'], + }); + + expect(api).toMatch( + /params:\s*{\s*\n\s*status:\s*queryArg\.status\s*\?\s*encodeURIComponent\(\s*String\(queryArg\.status\)\s*\)\s*:\s*undefined\s*,?\s*\n\s*}/s + ); + }); + + it('should not encode path parameters', async () => { + const api = await generateEndpoints({ + ...config, + filterEndpoints: ['getOrderById'], + }); + // eslint-disable-next-line no-template-curly-in-string + expect(api).toContain('`/store/order/${queryArg.orderId}`'); + }); + + it('should not encode body parameters', async () => { + const api = await generateEndpoints({ + ...config, + filterEndpoints: ['addPet'], + }); + expect(api).toContain('body: queryArg.pet'); + expect(api).not.toContain('body: encodeURIComponent(String(queryArg.pet))'); + }); + + it('should not encode query parameters when encodeQueryParams is false', async () => { + const api = await generateEndpoints({ + ...config, + encodeQueryParams: false, + filterEndpoints: ['findPetsByStatus', 'getOrderById'], + }); + expect(api).toContain('status: queryArg.status'); + }); +}); + describe('option flattenArg', () => { const config = { apiFile: './fixtures/emptyApi.ts',