diff --git a/lib/interfaces/swagger-document-options.interface.ts b/lib/interfaces/swagger-document-options.interface.ts index 59c6c6a91..4f0412198 100644 --- a/lib/interfaces/swagger-document-options.interface.ts +++ b/lib/interfaces/swagger-document-options.interface.ts @@ -18,4 +18,9 @@ export interface SwaggerDocumentOptions { * If `true`, swagger will also load routes from the modules imported by `include` modules */ deepScanRoutes?: boolean; + + /** + * If `true`, only uses the method name in operations (getUser) instead of methods prefixed by the controller name (UserController_getUser). + */ + useMethodNameForOperations?: boolean; } diff --git a/lib/swagger-explorer.ts b/lib/swagger-explorer.ts index e9ad56e9a..9ecb77f11 100644 --- a/lib/swagger-explorer.ts +++ b/lib/swagger-explorer.ts @@ -56,14 +56,17 @@ export class SwaggerExplorer { private readonly metadataScanner = new MetadataScanner(); private readonly schemas: SchemaObject[] = []; private readonly schemaRefsStack: string[] = []; + private useMethodNameForOperations: boolean; constructor(private readonly schemaObjectFactory: SchemaObjectFactory) {} public exploreController( wrapper: InstanceWrapper, modulePath?: string, - globalPrefix?: string + globalPrefix?: string, + useMethodNameForOperations?: boolean ) { + this.useMethodNameForOperations = useMethodNameForOperations; const { instance, metatype } = wrapper; const prototype = Object.getPrototypeOf(instance); const documentResolvers: DenormalizedDocResolvers = { @@ -226,7 +229,7 @@ export class SwaggerExplorer { } private getOperationId(instance: object, method: Function): string { - if (instance.constructor) { + if (instance.constructor && !this.useMethodNameForOperations) { return `${instance.constructor.name}_${method.name}`; } return method.name; diff --git a/lib/swagger-scanner.ts b/lib/swagger-scanner.ts index dcf9c199e..e80669e4c 100644 --- a/lib/swagger-scanner.ts +++ b/lib/swagger-scanner.ts @@ -32,7 +32,8 @@ export class SwaggerScanner { deepScanRoutes, include: includedModules = [], extraModels = [], - ignoreGlobalPrefix = false + ignoreGlobalPrefix = false, + useMethodNameForOperations = false } = options; const container: NestContainer = (app as any).container; @@ -64,7 +65,12 @@ export class SwaggerScanner { ? Reflect.getMetadata(MODULE_PATH, metatype) : undefined; - return this.scanModuleRoutes(allRoutes, path, globalPrefix); + return this.scanModuleRoutes( + allRoutes, + path, + globalPrefix, + useMethodNameForOperations + ); } ); @@ -85,10 +91,16 @@ export class SwaggerScanner { public scanModuleRoutes( routes: Map, modulePath?: string, - globalPrefix?: string + globalPrefix?: string, + useMethodNameForOperations?: boolean ): Array & Record<'root', any>> { const denormalizedArray = [...routes.values()].map((ctrl) => - this.explorer.exploreController(ctrl, modulePath, globalPrefix) + this.explorer.exploreController( + ctrl, + modulePath, + globalPrefix, + useMethodNameForOperations + ) ); return flatten(denormalizedArray) as any; } diff --git a/test/explorer/swagger-explorer.spec.ts b/test/explorer/swagger-explorer.spec.ts index 7340b30fd..6d9503195 100644 --- a/test/explorer/swagger-explorer.spec.ts +++ b/test/explorer/swagger-explorer.spec.ts @@ -19,12 +19,14 @@ import { ModelPropertiesAccessor } from '../../lib/services/model-properties-acc import { SchemaObjectFactory } from '../../lib/services/schema-object-factory'; import { SwaggerTypesMapper } from '../../lib/services/swagger-types-mapper'; import { SwaggerExplorer } from '../../lib/swagger-explorer'; +import { DenormalizedDoc } from '../../lib/interfaces/denormalized-doc.interface'; describe('SwaggerExplorer', () => { const schemaObjectFactory = new SchemaObjectFactory( new ModelPropertiesAccessor(), new SwaggerTypesMapper() ); + describe('when module only uses metadata', () => { class Foo {} @@ -76,11 +78,35 @@ describe('SwaggerExplorer', () => { } as InstanceWrapper, 'path' ); + const operationPrefix = 'FooController_'; + + validateRoutes(routes, operationPrefix); + }); + + it('sees two controller operations and their responses with useMethodNameForOperations = true', () => { + const explorer = new SwaggerExplorer(schemaObjectFactory); + const routes = explorer.exploreController( + { + instance: new FooController(), + metatype: FooController + } as InstanceWrapper, + 'path', + undefined, + true + ); + const operationPrefix = ''; + validateRoutes(routes, operationPrefix); + }); + + const validateRoutes = ( + routes: DenormalizedDoc[], + operationPrefix: string + ) => { expect(routes.length).toEqual(2); // POST - expect(routes[0].root.operationId).toEqual('FooController_create'); + expect(routes[0].root.operationId).toEqual(operationPrefix + 'create'); expect(routes[0].root.method).toEqual('post'); expect(routes[0].root.path).toEqual('/path/foos'); expect(routes[0].root.summary).toEqual('Create foo'); @@ -141,7 +167,7 @@ describe('SwaggerExplorer', () => { }); // GET - expect(routes[1].root.operationId).toEqual('FooController_find'); + expect(routes[1].root.operationId).toEqual(operationPrefix + 'find'); expect(routes[1].root.method).toEqual('get'); expect(routes[1].root.path).toEqual('/path/foos/{objectId}'); expect(routes[1].root.summary).toEqual('List all Foos'); @@ -179,8 +205,9 @@ describe('SwaggerExplorer', () => { } } }); - }); + }; }); + describe('when explicit decorators and metadata are used', () => { class Foo {} @@ -223,11 +250,35 @@ describe('SwaggerExplorer', () => { } as InstanceWrapper, 'path' ); + const prefix = 'FooController_'; + + validateRoutes(routes, prefix); + }); + + it('sees two controller operations and their responses when useMethodNameForOperations = true', () => { + const explorer = new SwaggerExplorer(schemaObjectFactory); + const routes = explorer.exploreController( + { + instance: new FooController(), + metatype: FooController + } as InstanceWrapper, + 'path', + undefined, + true + ); + const prefix = ''; + + validateRoutes(routes, prefix); + }); + const validateRoutes = ( + routes: DenormalizedDoc[], + operationPrefix: string + ) => { expect(routes.length).toEqual(2); // POST - expect(routes[0].root.operationId).toEqual('FooController_create'); + expect(routes[0].root.operationId).toEqual(operationPrefix + 'create'); expect(routes[0].root.method).toEqual('post'); expect(routes[0].root.path).toEqual('/path/foos'); expect(routes[0].root.summary).toEqual('Create foo'); @@ -260,7 +311,7 @@ describe('SwaggerExplorer', () => { }); // GET - expect(routes[1].root.operationId).toEqual('FooController_find'); + expect(routes[1].root.operationId).toEqual(operationPrefix + 'find'); expect(routes[1].root.method).toEqual('get'); expect(routes[1].root.path).toEqual('/path/foos/{objectId}'); expect(routes[1].root.summary).toEqual('List all Foos'); @@ -310,7 +361,7 @@ describe('SwaggerExplorer', () => { } } }); - }); + }; }); describe('when only explicit decorators are used', () => { class Foo {} @@ -351,11 +402,35 @@ describe('SwaggerExplorer', () => { } as InstanceWrapper, 'path' ); + const operationPrefix = 'FooController_'; + + validateRoutes(routes, operationPrefix); + }); + + it('sees two controller operations and their responses when useMethodNameForOperations = true', () => { + const explorer = new SwaggerExplorer(schemaObjectFactory); + const routes = explorer.exploreController( + { + instance: new FooController(), + metatype: FooController + } as InstanceWrapper, + 'path', + undefined, + true + ); + const operationPrefix = ''; + validateRoutes(routes, operationPrefix); + }); + + const validateRoutes = ( + routes: DenormalizedDoc[], + operationPrefix: string + ) => { expect(routes.length).toEqual(2); // POST - expect(routes[0].root.operationId).toEqual('FooController_create'); + expect(routes[0].root.operationId).toEqual(operationPrefix + 'create'); expect(routes[0].root.method).toEqual('post'); expect(routes[0].root.path).toEqual('/path/foos'); expect(routes[0].root.summary).toEqual('Create foo'); @@ -385,7 +460,7 @@ describe('SwaggerExplorer', () => { }); // GET - expect(routes[1].root.operationId).toEqual('FooController_find'); + expect(routes[1].root.operationId).toEqual(operationPrefix + 'find'); expect(routes[1].root.method).toEqual('get'); expect(routes[1].root.path).toEqual('/path/foos/{objectId}'); expect(routes[1].root.summary).toEqual('List all Foos'); @@ -424,7 +499,7 @@ describe('SwaggerExplorer', () => { } } }); - }); + }; }); describe('when custom properties are passed', () => { class Foo {} @@ -492,6 +567,25 @@ describe('SwaggerExplorer', () => { 'path' ); + validateRoutes(routes); + }); + + it('should merge implicit metadata with explicit options and useMethodNameForOperations = true', () => { + const explorer = new SwaggerExplorer(schemaObjectFactory); + const routes = explorer.exploreController( + { + instance: new FooController(), + metatype: FooController + } as InstanceWrapper, + 'path', + undefined, + true + ); + + validateRoutes(routes); + }); + + const validateRoutes = (routes: DenormalizedDoc[]) => { expect(routes.length).toEqual(2); // POST @@ -581,7 +675,7 @@ describe('SwaggerExplorer', () => { } } }); - }); + }; }); describe('when enum is used', () => { enum ParamEnum {