fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #946 from hey-api/chore/warn-duplicate-operation-ids

chore: warn on duplicate operation ID

authored by

Lubos and committed by
GitHub
9145f643 e0bdd51e

+78 -27
+5
.changeset/five-lamps-kneel.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + chore: warn on duplicate operation ID
+1
packages/openapi-ts/src/generate/__tests__/class.spec.ts
··· 34 34 35 35 const client: Parameters<typeof generateClientClass>[2] = { 36 36 models: [], 37 + operationIds: new Map(), 37 38 server: 'http://localhost:8080', 38 39 services: [], 39 40 types: {},
+3
packages/openapi-ts/src/generate/__tests__/core.spec.ts
··· 18 18 it('writes to filesystem', async () => { 19 19 const client: Parameters<typeof generateCore>[1] = { 20 20 models: [], 21 + operationIds: new Map(), 21 22 server: 'http://localhost:8080', 22 23 services: [], 23 24 types: {}, ··· 77 78 it('uses client server value for base', async () => { 78 79 const client: Parameters<typeof generateCore>[1] = { 79 80 models: [], 81 + operationIds: new Map(), 80 82 server: 'http://localhost:8080', 81 83 services: [], 82 84 types: {}, ··· 118 120 it('uses custom value for base', async () => { 119 121 const client: Parameters<typeof generateCore>[1] = { 120 122 models: [], 123 + operationIds: new Map(), 121 124 server: 'http://localhost:8080', 122 125 services: [], 123 126 types: {},
+1
packages/openapi-ts/src/generate/__tests__/output.spec.ts
··· 34 34 35 35 const client: Parameters<typeof generateOutput>[1] = { 36 36 models: [], 37 + operationIds: new Map(), 37 38 server: 'http://localhost:8080', 38 39 services: [], 39 40 types: {},
+2
packages/openapi-ts/src/generate/__tests__/services.spec.ts
··· 37 37 38 38 const client: Parameters<typeof generateServices>[0]['client'] = { 39 39 models: [], 40 + operationIds: new Map(), 40 41 server: 'http://localhost:8080', 41 42 services: [ 42 43 { ··· 117 118 118 119 const client: Parameters<typeof generateServices>[0]['client'] = { 119 120 models: [], 121 + operationIds: new Map(), 120 122 server: 'http://localhost:8080', 121 123 services: [ 122 124 {
+1
packages/openapi-ts/src/generate/__tests__/types.spec.ts
··· 59 59 type: 'User', 60 60 }, 61 61 ], 62 + operationIds: new Map(), 62 63 server: 'http://localhost:8080', 63 64 services: [], 64 65 types: {},
+1 -1
packages/openapi-ts/src/generate/transformers.ts
··· 263 263 if (nonVoidResponses.length > 1) { 264 264 if (config.debug) { 265 265 console.warn( 266 - `⚠️ Transformers warning: route ${operation.method} ${operation.path} has ${nonVoidResponses.length} non-void success responses. This is currently not handled and we will not generate a response transformer. Please open an issue if you'd like this feature https://github.com/hey-api/openapi-ts/issues`, 266 + `❗️ Transformers warning: route ${operation.method} ${operation.path} has ${nonVoidResponses.length} non-void success responses. This is currently not handled and we will not generate a response transformer. Please open an issue if you'd like this feature https://github.com/hey-api/openapi-ts/issues`, 267 267 ); 268 268 } 269 269 continue;
+1 -1
packages/openapi-ts/src/index.ts
··· 262 262 263 263 if (!useOptions) { 264 264 console.warn( 265 - '⚠️ Deprecation warning: useOptions set to false. This setting will be removed in future versions. Please migrate useOptions to true https://heyapi.vercel.app/openapi-ts/migrating.html#v0-27-38', 265 + '❗️ Deprecation warning: useOptions set to false. This setting will be removed in future versions. Please migrate useOptions to true https://heyapi.vercel.app/openapi-ts/migrating.html#v0-27-38', 266 266 ); 267 267 } 268 268
+2 -1
packages/openapi-ts/src/openApi/v2/index.ts
··· 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 16 const { models, types } = getModels(openApi); 17 - const services = getServices({ openApi, types }); 17 + const { operationIds, services } = getServices({ openApi, types }); 18 18 19 19 return { 20 20 models, 21 + operationIds, 21 22 server, 22 23 services, 23 24 types,
+1 -1
packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts
··· 26 26 useOptions: true, 27 27 }); 28 28 29 - const services = getServices({ 29 + const { services } = getServices({ 30 30 openApi: { 31 31 info: { 32 32 title: 'x',
+19 -4
packages/openapi-ts/src/openApi/v2/parser/getServices.ts
··· 16 16 }: { 17 17 openApi: OpenApi; 18 18 types: Client['types']; 19 - }): Service[] => { 19 + }): Pick<Client, 'operationIds' | 'services'> => { 20 20 const config = getConfig(); 21 21 22 22 const regexp = config.services.filter 23 23 ? new RegExp(config.services.filter) 24 24 : undefined; 25 25 26 + const operationIds = new Map<string, string>(); 26 27 const services = new Map<string, Service>(); 27 28 28 29 for (const url in openApi.paths) { ··· 36 37 for (const key in path) { 37 38 const method = key as Lowercase<Operation['method']>; 38 39 39 - const shouldProcess = 40 - !regexp || regexp.test(`${method.toUpperCase()} ${url}`); 40 + const operationKey = `${method.toUpperCase()} ${url}`; 41 + const shouldProcess = !regexp || regexp.test(operationKey); 41 42 42 43 if (shouldProcess && allowedServiceMethods.includes(method)) { 43 44 const op = path[method]!; 45 + 46 + if (op.operationId) { 47 + if (operationIds.has(op.operationId)) { 48 + console.warn( 49 + `❗️ Duplicate operationId: ${op.operationId} in ${operationKey}. Please ensure your operation IDs are unique. This behavior is not supported and will likely lead to unexpected results.`, 50 + ); 51 + } else { 52 + operationIds.set(op.operationId, operationKey); 53 + } 54 + } 55 + 44 56 const tags = 45 57 op.tags?.length && (config.services.asClass || config.name) 46 58 ? op.tags.filter(unique) ··· 66 78 } 67 79 } 68 80 69 - return Array.from(services.values()); 81 + return { 82 + operationIds, 83 + services: Array.from(services.values()), 84 + }; 70 85 };
+2 -1
packages/openapi-ts/src/openApi/v3/index.ts
··· 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 16 const { models, types } = getModels(openApi); 17 - const services = getServices({ openApi, types }); 17 + const { operationIds, services } = getServices({ openApi, types }); 18 18 19 19 return { 20 20 models, 21 + operationIds, 21 22 server, 22 23 services, 23 24 types,
+1 -1
packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts
··· 26 26 useOptions: true, 27 27 }); 28 28 29 - const services = getServices({ 29 + const { services } = getServices({ 30 30 openApi: { 31 31 info: { 32 32 title: 'x',
+19 -4
packages/openapi-ts/src/openApi/v3/parser/getServices.ts
··· 16 16 }: { 17 17 openApi: OpenApi; 18 18 types: Client['types']; 19 - }): Service[] => { 19 + }): Pick<Client, 'operationIds' | 'services'> => { 20 20 const config = getConfig(); 21 21 22 22 const regexp = config.services.filter 23 23 ? new RegExp(config.services.filter) 24 24 : undefined; 25 25 26 + const operationIds = new Map<string, string>(); 26 27 const services = new Map<string, Service>(); 27 28 28 29 for (const url in openApi.paths) { ··· 36 37 for (const key in path) { 37 38 const method = key as Lowercase<Operation['method']>; 38 39 39 - const shouldProcess = 40 - !regexp || regexp.test(`${method.toUpperCase()} ${url}`); 40 + const operationKey = `${method.toUpperCase()} ${url}`; 41 + const shouldProcess = !regexp || regexp.test(operationKey); 41 42 42 43 if (shouldProcess && allowedServiceMethods.includes(method)) { 43 44 const op = path[method]!; 45 + 46 + if (op.operationId) { 47 + if (operationIds.has(op.operationId)) { 48 + console.warn( 49 + `❗️ Duplicate operationId: ${op.operationId} in ${operationKey}. Please ensure your operation IDs are unique. This behavior is not supported and will likely lead to unexpected results.`, 50 + ); 51 + } else { 52 + operationIds.set(op.operationId, operationKey); 53 + } 54 + } 55 + 44 56 const tags = 45 57 op.tags?.length && (config.services.asClass || config.name) 46 58 ? op.tags.filter(unique) ··· 66 78 } 67 79 } 68 80 69 - return Array.from(services.values()); 81 + return { 82 + operationIds, 83 + services: Array.from(services.values()), 84 + }; 70 85 };
+6
packages/openapi-ts/src/types/client.ts
··· 3 3 4 4 export interface Client { 5 5 models: Model[]; 6 + /** 7 + * Map of unique operation IDs where operation IDs are keys. The values 8 + * are endpoints in the `${method} ${path}` format. This is used to detect 9 + * duplicate operation IDs in the specification. 10 + */ 11 + operationIds: Map<string, string>; 6 12 server: string; 7 13 services: Service[]; 8 14 /**
+8 -8
packages/openapi-ts/test/bin.spec.ts
··· 15 15 'true', 16 16 ]); 17 17 expect(result.stdout.toString()).toContain('Done!'); 18 - expect(result.stderr.toString()).toBe(''); 18 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 19 19 }); 20 20 21 21 it('generates angular client', () => { ··· 31 31 'true', 32 32 ]); 33 33 expect(result.stdout.toString()).toContain(''); 34 - expect(result.stderr.toString()).toBe(''); 34 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 35 35 }); 36 36 37 37 it('generates axios client', () => { ··· 47 47 'true', 48 48 ]); 49 49 expect(result.stdout.toString()).toContain(''); 50 - expect(result.stderr.toString()).toBe(''); 50 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 51 51 }); 52 52 53 53 it('generates fetch client', () => { ··· 63 63 'true', 64 64 ]); 65 65 expect(result.stdout.toString()).toContain(''); 66 - expect(result.stderr.toString()).toBe(''); 66 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 67 67 }); 68 68 69 69 it('generates node client', () => { ··· 79 79 'true', 80 80 ]); 81 81 expect(result.stdout.toString()).toContain(''); 82 - expect(result.stderr.toString()).toBe(''); 82 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 83 83 }); 84 84 85 85 it('generates xhr client', () => { ··· 95 95 'true', 96 96 ]); 97 97 expect(result.stdout.toString()).toContain(''); 98 - expect(result.stderr.toString()).toBe(''); 98 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 99 99 }); 100 100 101 101 it('supports all parameters', () => { ··· 120 120 'true', 121 121 ]); 122 122 expect(result.stdout.toString()).toContain('Done!'); 123 - expect(result.stderr.toString()).toBe(''); 123 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 124 124 }); 125 125 126 126 it('supports regexp parameters', () => { ··· 140 140 'true', 141 141 ]); 142 142 expect(result.stdout.toString()).toContain('Done!'); 143 - expect(result.stderr.toString()).toBe(''); 143 + expect(result.stderr.toString()).toContain('Duplicate operationId'); 144 144 }); 145 145 146 146 it('throws error without parameters', () => {
+5 -5
packages/openapi-ts/test/sample.cjs
··· 3 3 const main = async () => { 4 4 /** @type {import('../src/node/index').UserConfig} */ 5 5 const config = { 6 - // client: { 7 - // // bundle: true, 8 - // // name: '@hey-api/client-axios', 9 - // name: '@hey-api/client-fetch', 10 - // }, 6 + client: { 7 + // bundle: true, 8 + // name: '@hey-api/client-axios', 9 + name: '@hey-api/client-fetch', 10 + }, 11 11 // debug: true, 12 12 // input: './test/spec/v3-transforms.json', 13 13 input: './test/spec/v3.json',