Skip to content

Commit 2fea0ce

Browse files
kahirokunnphryneas
andauthored
Codegen: better handling of duplicate param names (#3780)
Co-authored-by: Lenz Weber-Tronic <mail@lenzw.de>
1 parent 49fa298 commit 2fea0ce

File tree

5 files changed

+107
-12
lines changed

5 files changed

+107
-12
lines changed

packages/rtk-query-codegen-openapi/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
"rtk-query-codegen-openapi": "lib/bin/cli.js"
1818
},
1919
"scripts": {
20-
"build": "tsc",
20+
"build": "tsc && chmod +x lib/bin/cli.js",
2121
"prepare": "npm run build && chmod +x ./lib/bin/cli.js",
2222
"format": "prettier --write \"src/**/*.ts\"",
23-
"test:update": "lib/bin/cli.js test/fixtures/petstore.json --file test/fixtures/generated.ts -h",
23+
"test:update": "jest --runInBand --updateSnapshot",
2424
"test:update:enum": "lib/bin/cli.js test/config.example.enum.ts",
2525
"test": "jest --runInBand",
2626
"cli": "esr src/bin/cli.ts"

packages/rtk-query-codegen-openapi/src/generate.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,27 @@ export async function generateApi(
258258

259259
const allNames = parameters.map((p) => p.name);
260260
const queryArg: QueryArgDefinitions = {};
261-
for (const param of parameters) {
262-
const isPureSnakeCase = /^[a-zA-Z][\w]*$/.test(param.name);
263-
const camelCaseName = camelCase(param.name);
264-
265-
const name = isPureSnakeCase && !allNames.includes(camelCaseName) ? camelCaseName : param.name;
261+
function generateName(name: string, potentialPrefix: string) {
262+
const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
263+
// prefix with `query`, `path` or `body` if there are multiple paramters with the same name
264+
const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
265+
if (hasNamingConflict) {
266+
name = `${potentialPrefix}_${name}`;
267+
}
268+
// convert to camelCase if the name is pure snake_case and there are no naming conflicts
269+
const camelCaseName = camelCase(name);
270+
if (isPureSnakeCase && !allNames.includes(camelCaseName)) {
271+
name = camelCaseName;
272+
}
273+
// if there are still any naming conflicts, prepend with underscore
274+
while (name in queryArg) {
275+
name = '_' + name;
276+
}
277+
return name;
278+
}
266279

280+
for (const param of parameters) {
281+
const name = generateName(param.name, param.in);
267282
queryArg[name] = {
268283
origin: 'param',
269284
name,
@@ -279,11 +294,7 @@ export async function generateApi(
279294
const schema = apiGen.getSchemaFromContent(body.content);
280295
const type = apiGen.getTypeFromSchema(schema);
281296
const schemaName = camelCase((type as any).name || getReferenceName(schema) || 'body');
282-
let name = schemaName in queryArg ? 'body' : schemaName;
283-
284-
while (name in queryArg) {
285-
name = '_' + name;
286-
}
297+
const name = generateName(schemaName in queryArg ? 'body' : schemaName, 'body');
287298

288299
queryArg[name] = {
289300
origin: 'body',
@@ -479,6 +490,7 @@ type QueryArgDefinition = {
479490
originalName: string;
480491
type: ts.TypeNode;
481492
required?: boolean;
493+
param?: OpenAPIV3.ParameterObject;
482494
} & (
483495
| {
484496
origin: 'param';

packages/rtk-query-codegen-openapi/test/__snapshots__/generateEndpoints.test.ts.snap

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,39 @@ export type User = {
263263

264264
`;
265265

266+
exports[`duplicate parameter names must be prefixed with a path or query prefix 1`] = `
267+
import { api } from './fixtures/emptyApi';
268+
const injectedRtkApi = api.injectEndpoints({
269+
endpoints: (build) => ({
270+
patchApiV1ListByItemId: build.mutation<PatchApiV1ListByItemIdApiResponse, PatchApiV1ListByItemIdApiArg>({
271+
query: (queryArg) => ({
272+
url: \`/api/v1/list/\${queryArg['item.id']}\`,
273+
method: 'PATCH',
274+
}),
275+
}),
276+
patchApiV2BySomeName: build.mutation<PatchApiV2BySomeNameApiResponse, PatchApiV2BySomeNameApiArg>({
277+
query: (queryArg) => ({
278+
url: \`/api/v2/\${queryArg.pathSomeName}\`,
279+
method: 'PATCH',
280+
params: { some_name: queryArg.querySomeName },
281+
}),
282+
}),
283+
}),
284+
overrideExisting: false,
285+
});
286+
export { injectedRtkApi as enhancedApi };
287+
export type PatchApiV1ListByItemIdApiResponse = /** status 200 A successful response. */ string;
288+
export type PatchApiV1ListByItemIdApiArg = {
289+
'item.id': string;
290+
};
291+
export type PatchApiV2BySomeNameApiResponse = /** status 200 A successful response. */ string;
292+
export type PatchApiV2BySomeNameApiArg = {
293+
pathSomeName: string;
294+
querySomeName: string;
295+
};
296+
297+
`;
298+
266299
exports[`endpoint filtering: should only have endpoints loginUser, placeOrder, getOrderById, deleteOrder 1`] = `
267300
import { api } from './fixtures/emptyApi';
268301
const injectedRtkApi = api.injectEndpoints({
@@ -434,6 +467,13 @@ const injectedRtkApi = api.injectEndpoints({
434467
method: 'PATCH',
435468
}),
436469
}),
470+
patchApiV2BySomeName: build.mutation<PatchApiV2BySomeNameApiResponse, PatchApiV2BySomeNameApiArg>({
471+
query: (queryArg) => ({
472+
url: \`/api/v2/\${queryArg.pathSomeName}\`,
473+
method: 'PATCH',
474+
params: { some_name: queryArg.querySomeName },
475+
}),
476+
}),
437477
}),
438478
overrideExisting: false,
439479
});
@@ -442,6 +482,11 @@ export type PatchApiV1ListByItemIdApiResponse = /** status 200 A successful resp
442482
export type PatchApiV1ListByItemIdApiArg = {
443483
'item.id': string;
444484
};
485+
export type PatchApiV2BySomeNameApiResponse = /** status 200 A successful response. */ string;
486+
export type PatchApiV2BySomeNameApiArg = {
487+
pathSomeName: string;
488+
querySomeName: string;
489+
};
445490

446491
`;
447492

packages/rtk-query-codegen-openapi/test/fixtures/params.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@
2727
}
2828
]
2929
}
30+
},
31+
"/api/v2/{some_name}": {
32+
"patch": {
33+
"responses": {
34+
"200": {
35+
"description": "A successful response.",
36+
"schema": {
37+
"type": "string"
38+
}
39+
}
40+
},
41+
"parameters": [
42+
{
43+
"name": "some_name",
44+
"in": "path",
45+
"required": true,
46+
"type": "string"
47+
},
48+
{
49+
"name": "some_name",
50+
"in": "query",
51+
"required": true,
52+
"type": "string"
53+
}
54+
]
55+
}
3056
}
3157
}
3258
}

packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ test('should use brackets in a querystring urls arg, when the arg contains full
229229
expect(api).toMatchSnapshot();
230230
});
231231

232+
test('duplicate parameter names must be prefixed with a path or query prefix', async () => {
233+
const api = await generateEndpoints({
234+
unionUndefined: true,
235+
apiFile: './fixtures/emptyApi.ts',
236+
schemaFile: resolve(__dirname, 'fixtures/params.json'),
237+
});
238+
// eslint-disable-next-line no-template-curly-in-string
239+
expect(api).toContain('pathSomeName: string');
240+
expect(api).toContain('querySomeName: string');
241+
expect(api).toMatchSnapshot();
242+
});
243+
232244
test('apiImport builds correct `import` statement', async () => {
233245
const api = await generateEndpoints({
234246
unionUndefined: true,

0 commit comments

Comments
 (0)