Skip to content

Commit

Permalink
Merge pull request #2549 from deongroenewald/operation-ids-with-version
Browse files Browse the repository at this point in the history
feat(swagger): Provide URI version to operationIdFactory
  • Loading branch information
kamilmysliwiec authored Aug 7, 2023
2 parents 1fb289e + 7cbe0ef commit 062dcb6
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 11 deletions.
8 changes: 7 additions & 1 deletion lib/interfaces/swagger-document-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type OperationIdFactory = (
controllerKey: string,
methodKey: string,
version?: string
) => string;

export interface SwaggerDocumentOptions {
/**
* List of modules to include in the specification
Expand All @@ -24,5 +30,5 @@ export interface SwaggerDocumentOptions {
* based on the `controllerKey` and `methodKey`
* @default () => controllerKey_methodKey
*/
operationIdFactory?: (controllerKey: string, methodKey: string) => string;
operationIdFactory?: OperationIdFactory;
}
59 changes: 51 additions & 8 deletions lib/swagger-explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
Controller,
Type,
VERSION_NEUTRAL,
VersioningOptions,
VersionValue
} from '@nestjs/common/interfaces';
Expand Down Expand Up @@ -53,6 +54,7 @@ import {
exploreApiTagsMetadata,
exploreGlobalApiTagsMetadata
} from './explorers/api-use-tags.explorer';
import { OperationIdFactory } from './interfaces';
import { DenormalizedDocResolvers } from './interfaces/denormalized-doc-resolvers.interface';
import { DenormalizedDoc } from './interfaces/denormalized-doc.interface';
import {
Expand All @@ -68,8 +70,10 @@ export class SwaggerExplorer {
private readonly mimetypeContentWrapper = new MimetypeContentWrapper();
private readonly metadataScanner = new MetadataScanner();
private readonly schemas: Record<string, SchemaObject> = {};
private operationIdFactory = (controllerKey: string, methodKey: string) =>
controllerKey ? `${controllerKey}_${methodKey}` : methodKey;
private operationIdFactory: OperationIdFactory = (
controllerKey: string,
methodKey: string
) => (controllerKey ? `${controllerKey}_${methodKey}` : methodKey);
private routePathFactory?: RoutePathFactory;

constructor(private readonly schemaObjectFactory: SchemaObjectFactory) {}
Expand All @@ -79,7 +83,7 @@ export class SwaggerExplorer {
applicationConfig: ApplicationConfig,
modulePath?: string | undefined,
globalPrefix?: string | undefined,
operationIdFactory?: (controllerKey: string, methodKey: string) => string
operationIdFactory?: OperationIdFactory
) {
this.routePathFactory = new RoutePathFactory(applicationConfig);
if (operationIdFactory) {
Expand Down Expand Up @@ -271,10 +275,18 @@ export class SwaggerExplorer {
VERSION_METADATA,
method
);
const versioningOptions = applicationConfig.getVersioning();
const controllerVersion = this.getVersionMetadata(
metatype,
applicationConfig.getVersioning()
versioningOptions
);

const versionOrVersions = methodVersion ?? controllerVersion;
const versions = this.getRoutePathVersions(
versionOrVersions,
versioningOptions
);

const allRoutePaths = this.routePathFactory.create(
{
methodPath,
Expand Down Expand Up @@ -302,27 +314,58 @@ export class SwaggerExplorer {
return validMethods.map((meth) => ({
method: meth.toLowerCase(),
path: fullPath === '' ? '/' : fullPath,
operationId: `${this.getOperationId(instance, method)}_${meth.toLowerCase()}`,
operationId: `${this.getOperationId(
instance,
method
)}_${meth.toLowerCase()}`,
...apiExtension
}));
}
const pathVersion = versions.find((v) => fullPath.includes(`/${v}/`));
return {
method: RequestMethod[requestMethod].toLowerCase(),
path: fullPath === '' ? '/' : fullPath,
operationId: this.getOperationId(instance, method),
operationId: this.getOperationId(instance, method, pathVersion),
...apiExtension
};
})
);
}

private getOperationId(instance: object, method: Function): string {
private getOperationId(
instance: object,
method: Function,
version?: string
): string {
return this.operationIdFactory(
instance.constructor?.name || '',
method.name
method.name,
version
);
}

private getRoutePathVersions(
versionValue?: VersionValue,
versioningOptions?: VersioningOptions
) {
let versions: string[] = [];

if (!versionValue || versioningOptions?.type !== VersioningType.URI) {
return versions;
}

if (Array.isArray(versionValue)) {
versions = versionValue.filter((v) => v !== VERSION_NEUTRAL) as string[];
} else if (versionValue !== VERSION_NEUTRAL) {
versions = [versionValue];
}

const prefix = this.routePathFactory.getVersionPrefix(versioningOptions);
versions = versions.map((v) => `${prefix}${v}`);

return versions;
}

private reflectControllerPath(metatype: Type<unknown>): string {
return Reflect.getMetadata(PATH_METADATA, metatype);
}
Expand Down
8 changes: 6 additions & 2 deletions lib/swagger-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { ApplicationConfig, NestContainer } from '@nestjs/core';
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { Module } from '@nestjs/core/injector/module';
import { flatten, isEmpty } from 'lodash';
import { OpenAPIObject, SwaggerDocumentOptions } from './interfaces';
import {
OpenAPIObject,
OperationIdFactory,
SwaggerDocumentOptions
} from './interfaces';
import { ModuleRoute } from './interfaces/module-route.interface';
import {
ReferenceObject,
Expand Down Expand Up @@ -106,7 +110,7 @@ export class SwaggerScanner {
modulePath: string | undefined,
globalPrefix: string | undefined,
applicationConfig: ApplicationConfig,
operationIdFactory?: (controllerKey: string, methodKey: string) => string
operationIdFactory?: OperationIdFactory
): ModuleRoute[] {
const denormalizedArray = [...controller.values()].map((ctrl) =>
this.explorer.exploreController(
Expand Down
Loading

0 comments on commit 062dcb6

Please sign in to comment.