Skip to content

Commit

Permalink
Merge pull request #1634 from glebbash/fix/colon-escape
Browse files Browse the repository at this point in the history
fix(): unescape colons in paths
  • Loading branch information
kamilmysliwiec authored Mar 25, 2022
2 parents 1ba8a02 + b11c809 commit 83a46f7
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 5 deletions.
44 changes: 44 additions & 0 deletions e2e/api-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,50 @@
}
}
},
"/api/v1/express:colon:another/{prop}": {
"get": {
"operationId": "AppController_withColonExpress",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/v2/express:colon:another/{prop}": {
"get": {
"operationId": "AppController_withColonExpress",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/v1/fastify:{colon}:{another}/{prop}": {
"get": {
"operationId": "AppController_withColonFastify",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/v2/fastify:{colon}:{another}/{prop}": {
"get": {
"operationId": "AppController_withColonFastify",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/cats": {
"post": {
"operationId": "CatsController_create",
Expand Down
5 changes: 5 additions & 0 deletions e2e/fastify.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ describe('Fastify Swagger', () => {
}
});

it('should fix colons in url', async () => {
const document = SwaggerModule.createDocument(app, builder.build());
expect(document.paths['/fastify:colon:another/{prop}']).toBeDefined();
});

it('should pass uiConfig options to fastify-swagger', async () => {
const document1 = SwaggerModule.createDocument(app, builder.build());
const uiConfig = {
Expand Down
10 changes: 10 additions & 0 deletions e2e/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ export class AppController {
withAliases(): string {
return 'Hello world!';
}

@Get('express[:]colon[:]another/:prop')
withColonExpress(): string {
return 'Hello world!';
}

@Get('fastify::colon::another/:prop')
withColonFastify(): string {
return 'Hello world!';
}
}
5 changes: 5 additions & 0 deletions e2e/validate-schema.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ describe('Validate OpenAPI schema', () => {
}
});

it('should fix colons in url', async () => {
const document = SwaggerModule.createDocument(app, options);
expect(document.paths['/api/v1/express:colon:another/{prop}']).toBeDefined();
});

it('should merge custom components passed via config', async () => {
const components = {
schemas: {
Expand Down
4 changes: 4 additions & 0 deletions lib/interfaces/module-route.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { OpenAPIObject } from '.';

export type ModuleRoute = Omit<OpenAPIObject, 'openapi' | 'info'> &
Record<'root', any>;
12 changes: 7 additions & 5 deletions lib/swagger-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { InstanceToken, Module } from '@nestjs/core/injector/module';
import { flatten, isEmpty } from 'lodash';
import { OpenAPIObject, SwaggerDocumentOptions } from './interfaces';
import { ModuleRoute } from './interfaces/module-route.interface';
import {
ReferenceObject,
SchemaObject
Expand All @@ -18,7 +19,7 @@ import { getGlobalPrefix } from './utils/get-global-prefix';
import { stripLastSlash } from './utils/strip-last-slash.util';

export class SwaggerScanner {
private readonly transfomer = new SwaggerTransformer();
private readonly transformer = new SwaggerTransformer();
private readonly schemaObjectFactory = new SchemaObjectFactory(
new ModelPropertiesAccessor(),
new SwaggerTypesMapper()
Expand Down Expand Up @@ -50,7 +51,7 @@ export class SwaggerScanner {

const denormalizedPaths = modules.map(
({ routes, metatype, relatedModules }) => {
let result = [];
let result: ModuleRoute[] = [];

if (deepScanRoutes) {
// only load submodules routes if asked
Expand All @@ -76,7 +77,7 @@ export class SwaggerScanner {
});
}
const modulePath = this.getModulePathMetadata(container, metatype);
return result.concat(
result = result.concat(
this.scanModuleRoutes(
routes,
modulePath,
Expand All @@ -85,14 +86,15 @@ export class SwaggerScanner {
operationIdFactory
)
);
return this.transformer.unescapeColonsInPath(app, result);
}
);

const schemas = this.explorer.getSchemas();
this.addExtraModels(schemas, extraModels);

return {
...this.transfomer.normalizePaths(flatten(denormalizedPaths)),
...this.transformer.normalizePaths(flatten(denormalizedPaths)),
components: {
schemas: schemas as Record<string, SchemaObject | ReferenceObject>
}
Expand All @@ -105,7 +107,7 @@ export class SwaggerScanner {
globalPrefix: string | undefined,
applicationConfig: ApplicationConfig,
operationIdFactory?: (controllerKey: string, methodKey: string) => string
): Array<Omit<OpenAPIObject, 'openapi' | 'info'> & Record<'root', any>> {
): ModuleRoute[] {
const denormalizedArray = [...routes.values()].map((ctrl) =>
this.explorer.exploreController(
ctrl,
Expand Down
21 changes: 21 additions & 0 deletions lib/swagger-transformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { INestApplication } from '@nestjs/common';
import { filter, groupBy, keyBy, mapValues, omit } from 'lodash';
import { OpenAPIObject } from './interfaces';
import { ModuleRoute } from './interfaces/module-route.interface';

export class SwaggerTransformer {
public normalizePaths(
Expand All @@ -26,4 +28,23 @@ export class SwaggerTransformer {
paths
};
}

public unescapeColonsInPath(
app: INestApplication,
moduleRoutes: ModuleRoute[]
): ModuleRoute[] {
const httpAdapter = app.getHttpAdapter();
const usingFastify = httpAdapter && httpAdapter.getType() === 'fastify';
const unescapeColon = usingFastify
? (path: string) => path.replace(/:\{([^}]+)\}/g, ':$1')
: (path: string) => path.replace(/\[:\]/g, ':');

return moduleRoutes.map((moduleRoute) => ({
...moduleRoute,
root: {
...moduleRoute.root,
path: unescapeColon(moduleRoute.root.path)
}
}));
}
}

0 comments on commit 83a46f7

Please sign in to comment.