diff --git a/bin/index.js b/bin/index.js index ce1c2ea81..56b9a498f 100755 --- a/bin/index.js +++ b/bin/index.js @@ -20,6 +20,7 @@ const params = program .option('--exportCore ', 'Write core files to disk', true) .option('--exportServices ', 'Write services to disk', true) .option('--exportModels ', 'Write models to disk', true) + .option('--useOperationId ', 'Use operation id to generate operation names', true) .option('--exportSchemas ', 'Write schemas to disk', false) .option('--indent ', 'Indentation options [4, 2, tabs]', '4') .option('--postfixServices ', 'Service name postfix', 'Service') @@ -51,6 +52,7 @@ if (OpenAPI) { exportServices: parseBooleanOrString(params.exportServices), exportModels: parseBooleanOrString(params.exportModels), exportSchemas: JSON.parse(params.exportSchemas) === true, + useOperationId: JSON.parse(params.useOperationId) === true, indent: params.indent, postfixServices: params.postfixServices, postfixModels: params.postfixModels, diff --git a/src/index.ts b/src/index.ts index f6b9b27b5..31c1bf9c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ export type Options = { exportServices?: boolean | string; exportModels?: boolean | string; exportSchemas?: boolean; + useOperationId?: boolean; indent?: Indent; postfixServices?: string; postfixModels?: string; @@ -45,6 +46,7 @@ export type Options = { * @param exportServices Generate services * @param exportModels Generate models * @param exportSchemas Generate schemas + * @param ignoreOperationId Ignore operationId * @param indent Indentation options (4, 2 or tab) * @param postfixServices Service name postfix * @param postfixModels Model name postfix @@ -63,6 +65,7 @@ export const generate = async ({ exportServices = true, exportModels = true, exportSchemas = false, + useOperationId = true, indent = Indent.SPACE_4, postfixServices = 'Service', postfixModels = '', @@ -92,7 +95,7 @@ export const generate = async ({ } if (parser) { - const client = parser(openApi); + const client = parser(openApi, useOperationId); const clientFinal = postProcessClient(client); if (write) { await writeClient( diff --git a/src/openApi/v2/index.ts b/src/openApi/v2/index.ts index 9dbdadb34..26eea55a0 100644 --- a/src/openApi/v2/index.ts +++ b/src/openApi/v2/index.ts @@ -8,13 +8,14 @@ import { getServiceVersion } from './parser/getServiceVersion'; /** * Parse the OpenAPI specification to a Client model that contains * all the models, services and schema's we should output. - * @param openApi The OpenAPI spec that we have loaded from disk. + * @param openApi The OpenAPI spec that we have loaded from disk. + * @param useOperationId should the operationId be used when generating operation names */ -export const parse = (openApi: OpenApi): Client => { +export const parse = (openApi: OpenApi, useOperationId: boolean): Client => { const version = getServiceVersion(openApi.info.version); const server = getServer(openApi); const models = getModels(openApi); - const services = getServices(openApi); + const services = getServices(openApi, useOperationId); return { version, server, models, services }; }; diff --git a/src/openApi/v2/parser/getOperation.ts b/src/openApi/v2/parser/getOperation.ts index 9aa157460..b1e147fff 100644 --- a/src/openApi/v2/parser/getOperation.ts +++ b/src/openApi/v2/parser/getOperation.ts @@ -17,10 +17,11 @@ export const getOperation = ( method: string, tag: string, op: OpenApiOperation, - pathParams: OperationParameters + pathParams: OperationParameters, + useOperationId: boolean ): Operation => { const serviceName = getServiceName(tag); - const operationName = getOperationName(url, method, op.operationId); + const operationName = getOperationName(url, method, useOperationId, op.operationId); // Create a new operation object for this method. const operation: Operation = { diff --git a/src/openApi/v2/parser/getOperationName.spec.ts b/src/openApi/v2/parser/getOperationName.spec.ts index bdaecb8f8..1cd946463 100644 --- a/src/openApi/v2/parser/getOperationName.spec.ts +++ b/src/openApi/v2/parser/getOperationName.spec.ts @@ -2,26 +2,32 @@ import { getOperationName } from './getOperationName'; describe('getOperationName', () => { it('should produce correct result', () => { - expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers'); - expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers'); - expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers'); - expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers'); - expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users'); - expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users'); - expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users'); - expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'GetAllUsers')).toEqual('getAllUsers'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, undefined)).toEqual('getApiUsers'); + expect(getOperationName('/api/v{api-version}/users', 'POST', true, undefined)).toEqual('postApiUsers'); + expect(getOperationName('/api/v1/users', 'GET', true, 'GetAllUsers')).toEqual('getAllUsers'); + expect(getOperationName('/api/v1/users', 'GET', true, undefined)).toEqual('getApiV1Users'); + expect(getOperationName('/api/v1/users', 'POST', true, undefined)).toEqual('postApiV1Users'); + expect(getOperationName('/api/v1/users/{id}', 'GET', true, undefined)).toEqual('getApiV1UsersById'); + expect(getOperationName('/api/v1/users/{id}', 'POST', true, undefined)).toEqual('postApiV1UsersById'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'fooBar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'FooBar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'Foo Bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo-bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo_bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '@foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '$foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '_foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '-foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '123.foo.bar')).toEqual('fooBar'); + + expect(getOperationName('/api/v1/users', 'GET', false, 'GetAllUsers')).toEqual('getApiV1Users'); + expect(getOperationName('/api/v{api-version}/users', 'GET', false, 'fooBar')).toEqual('getApiUsers'); + expect( + getOperationName('/api/v{api-version}/users/{userId}/location/{locationId}', 'GET', false, 'fooBar') + ).toEqual('getApiUsersByUserIdLocationByLocationId'); }); }); diff --git a/src/openApi/v2/parser/getOperationName.ts b/src/openApi/v2/parser/getOperationName.ts index 4734bfbc9..fb54bb575 100644 --- a/src/openApi/v2/parser/getOperationName.ts +++ b/src/openApi/v2/parser/getOperationName.ts @@ -7,14 +7,19 @@ import sanitizeOperationName from '../../../utils/sanitizeOperationName'; * This will use the operation ID - if available - and otherwise fallback * on a generated name from the URL */ -export const getOperationName = (url: string, method: string, operationId?: string): string => { - if (operationId) { +export const getOperationName = ( + url: string, + method: string, + useOperationId: boolean, + operationId?: string +): string => { + if (useOperationId && operationId) { return camelCase(sanitizeOperationName(operationId).trim()); } const urlWithoutPlaceholders = url .replace(/[^/]*?{api-version}.*?\//g, '') - .replace(/{(.*?)}/g, '') + .replace(/{(.*?)}/g, 'by-$1') .replace(/\//g, '-'); return camelCase(`${method}-${urlWithoutPlaceholders}`); diff --git a/src/openApi/v2/parser/getServices.spec.ts b/src/openApi/v2/parser/getServices.spec.ts index 915ee6b09..0d10d810b 100644 --- a/src/openApi/v2/parser/getServices.spec.ts +++ b/src/openApi/v2/parser/getServices.spec.ts @@ -2,28 +2,31 @@ import { getServices } from './getServices'; describe('getServices', () => { it('should create a unnamed service if tags are empty', () => { - const services = getServices({ - swagger: '2.0', - info: { - title: 'x', - version: '1', - }, - paths: { - '/api/trips': { - get: { - tags: [], - responses: { - 200: { - description: 'x', - }, - default: { - description: 'default', + const services = getServices( + { + swagger: '2.0', + info: { + title: 'x', + version: '1', + }, + paths: { + '/api/trips': { + get: { + tags: [], + responses: { + 200: { + description: 'x', + }, + default: { + description: 'default', + }, }, }, }, }, }, - }); + false + ); expect(services).toHaveLength(1); expect(services[0].name).toEqual('Default'); diff --git a/src/openApi/v2/parser/getServices.ts b/src/openApi/v2/parser/getServices.ts index d8fe411bb..596537cab 100644 --- a/src/openApi/v2/parser/getServices.ts +++ b/src/openApi/v2/parser/getServices.ts @@ -7,7 +7,7 @@ import { getOperationParameters } from './getOperationParameters'; /** * Get the OpenAPI services */ -export const getServices = (openApi: OpenApi): Service[] => { +export const getServices = (openApi: OpenApi, useOperationId: boolean): Service[] => { const services = new Map(); for (const url in openApi.paths) { if (openApi.paths.hasOwnProperty(url)) { @@ -30,7 +30,15 @@ export const getServices = (openApi: OpenApi): Service[] => { const op = path[method]!; const tags = op.tags?.length ? op.tags.filter(unique) : ['Default']; tags.forEach(tag => { - const operation = getOperation(openApi, url, method, tag, op, pathParams); + const operation = getOperation( + openApi, + url, + method, + tag, + op, + pathParams, + useOperationId + ); // If we have already declared a service, then we should fetch that and // append the new method to it. Otherwise we should create a new service object. diff --git a/src/openApi/v3/index.ts b/src/openApi/v3/index.ts index 9dbdadb34..26eea55a0 100644 --- a/src/openApi/v3/index.ts +++ b/src/openApi/v3/index.ts @@ -8,13 +8,14 @@ import { getServiceVersion } from './parser/getServiceVersion'; /** * Parse the OpenAPI specification to a Client model that contains * all the models, services and schema's we should output. - * @param openApi The OpenAPI spec that we have loaded from disk. + * @param openApi The OpenAPI spec that we have loaded from disk. + * @param useOperationId should the operationId be used when generating operation names */ -export const parse = (openApi: OpenApi): Client => { +export const parse = (openApi: OpenApi, useOperationId: boolean): Client => { const version = getServiceVersion(openApi.info.version); const server = getServer(openApi); const models = getModels(openApi); - const services = getServices(openApi); + const services = getServices(openApi, useOperationId); return { version, server, models, services }; }; diff --git a/src/openApi/v3/parser/getOperation.ts b/src/openApi/v3/parser/getOperation.ts index aee4bd0c2..27f7ad528 100644 --- a/src/openApi/v3/parser/getOperation.ts +++ b/src/openApi/v3/parser/getOperation.ts @@ -20,10 +20,11 @@ export const getOperation = ( method: string, tag: string, op: OpenApiOperation, - pathParams: OperationParameters + pathParams: OperationParameters, + useOperationId: boolean ): Operation => { const serviceName = getServiceName(tag); - const operationName = getOperationName(url, method, op.operationId); + const operationName = getOperationName(url, method, useOperationId, op.operationId); // Create a new operation object for this method. const operation: Operation = { diff --git a/src/openApi/v3/parser/getOperationName.spec.ts b/src/openApi/v3/parser/getOperationName.spec.ts index bdaecb8f8..1cd946463 100644 --- a/src/openApi/v3/parser/getOperationName.spec.ts +++ b/src/openApi/v3/parser/getOperationName.spec.ts @@ -2,26 +2,32 @@ import { getOperationName } from './getOperationName'; describe('getOperationName', () => { it('should produce correct result', () => { - expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers'); - expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers'); - expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers'); - expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers'); - expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users'); - expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users'); - expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users'); - expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'GetAllUsers')).toEqual('getAllUsers'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, undefined)).toEqual('getApiUsers'); + expect(getOperationName('/api/v{api-version}/users', 'POST', true, undefined)).toEqual('postApiUsers'); + expect(getOperationName('/api/v1/users', 'GET', true, 'GetAllUsers')).toEqual('getAllUsers'); + expect(getOperationName('/api/v1/users', 'GET', true, undefined)).toEqual('getApiV1Users'); + expect(getOperationName('/api/v1/users', 'POST', true, undefined)).toEqual('postApiV1Users'); + expect(getOperationName('/api/v1/users/{id}', 'GET', true, undefined)).toEqual('getApiV1UsersById'); + expect(getOperationName('/api/v1/users/{id}', 'POST', true, undefined)).toEqual('postApiV1UsersById'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar'); - expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'fooBar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'FooBar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'Foo Bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo-bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo_bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, 'foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '@foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '$foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '_foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '-foo.bar')).toEqual('fooBar'); + expect(getOperationName('/api/v{api-version}/users', 'GET', true, '123.foo.bar')).toEqual('fooBar'); + + expect(getOperationName('/api/v1/users', 'GET', false, 'GetAllUsers')).toEqual('getApiV1Users'); + expect(getOperationName('/api/v{api-version}/users', 'GET', false, 'fooBar')).toEqual('getApiUsers'); + expect( + getOperationName('/api/v{api-version}/users/{userId}/location/{locationId}', 'GET', false, 'fooBar') + ).toEqual('getApiUsersByUserIdLocationByLocationId'); }); }); diff --git a/src/openApi/v3/parser/getOperationName.ts b/src/openApi/v3/parser/getOperationName.ts index 4734bfbc9..fb54bb575 100644 --- a/src/openApi/v3/parser/getOperationName.ts +++ b/src/openApi/v3/parser/getOperationName.ts @@ -7,14 +7,19 @@ import sanitizeOperationName from '../../../utils/sanitizeOperationName'; * This will use the operation ID - if available - and otherwise fallback * on a generated name from the URL */ -export const getOperationName = (url: string, method: string, operationId?: string): string => { - if (operationId) { +export const getOperationName = ( + url: string, + method: string, + useOperationId: boolean, + operationId?: string +): string => { + if (useOperationId && operationId) { return camelCase(sanitizeOperationName(operationId).trim()); } const urlWithoutPlaceholders = url .replace(/[^/]*?{api-version}.*?\//g, '') - .replace(/{(.*?)}/g, '') + .replace(/{(.*?)}/g, 'by-$1') .replace(/\//g, '-'); return camelCase(`${method}-${urlWithoutPlaceholders}`); diff --git a/src/openApi/v3/parser/getServices.spec.ts b/src/openApi/v3/parser/getServices.spec.ts index baea07e66..abd49fb91 100644 --- a/src/openApi/v3/parser/getServices.spec.ts +++ b/src/openApi/v3/parser/getServices.spec.ts @@ -1,29 +1,33 @@ +/* eslint-disable */ import { getServices } from './getServices'; describe('getServices', () => { it('should create a unnamed service if tags are empty', () => { - const services = getServices({ - openapi: '3.0.0', - info: { - title: 'x', - version: '1', - }, - paths: { - '/api/trips': { - get: { - tags: [], - responses: { - 200: { - description: 'x', - }, - default: { - description: 'default', + const services = getServices( + { + openapi: '3.0.0', + info: { + title: 'x', + version: '1', + }, + paths: { + '/api/trips': { + get: { + tags: [], + responses: { + 200: { + description: 'x', + }, + default: { + description: 'default', + }, }, }, }, }, }, - }); + false, + ); expect(services).toHaveLength(1); expect(services[0].name).toEqual('Default'); diff --git a/src/openApi/v3/parser/getServices.ts b/src/openApi/v3/parser/getServices.ts index d8fe411bb..596537cab 100644 --- a/src/openApi/v3/parser/getServices.ts +++ b/src/openApi/v3/parser/getServices.ts @@ -7,7 +7,7 @@ import { getOperationParameters } from './getOperationParameters'; /** * Get the OpenAPI services */ -export const getServices = (openApi: OpenApi): Service[] => { +export const getServices = (openApi: OpenApi, useOperationId: boolean): Service[] => { const services = new Map(); for (const url in openApi.paths) { if (openApi.paths.hasOwnProperty(url)) { @@ -30,7 +30,15 @@ export const getServices = (openApi: OpenApi): Service[] => { const op = path[method]!; const tags = op.tags?.length ? op.tags.filter(unique) : ['Default']; tags.forEach(tag => { - const operation = getOperation(openApi, url, method, tag, op, pathParams); + const operation = getOperation( + openApi, + url, + method, + tag, + op, + pathParams, + useOperationId + ); // If we have already declared a service, then we should fetch that and // append the new method to it. Otherwise we should create a new service object.