From d3aae1d7c93361a7f511876cbbded45040e5e523 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 14:53:21 -0300 Subject: [PATCH 01/11] feat(core): add version attribute to routes mapper ensure routes mapper returns route's version when defined --- .../middleware/middleware-configuration.interface.ts | 2 ++ packages/core/middleware/routes-mapper.ts | 10 ++++++++-- packages/core/test/middleware/routes-mapper.spec.ts | 11 ++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/common/interfaces/middleware/middleware-configuration.interface.ts b/packages/common/interfaces/middleware/middleware-configuration.interface.ts index aef5012edec..15414b6cb07 100644 --- a/packages/common/interfaces/middleware/middleware-configuration.interface.ts +++ b/packages/common/interfaces/middleware/middleware-configuration.interface.ts @@ -1,9 +1,11 @@ import { RequestMethod } from '../../enums'; import { Type } from '../type.interface'; +import { VersionValue } from '../version-options.interface'; export interface RouteInfo { path: string; method: RequestMethod; + version?: VersionValue; } export interface MiddlewareConfiguration { diff --git a/packages/core/middleware/routes-mapper.ts b/packages/core/middleware/routes-mapper.ts index bbb8ea36cf2..e32a73096df 100644 --- a/packages/core/middleware/routes-mapper.ts +++ b/packages/core/middleware/routes-mapper.ts @@ -1,5 +1,5 @@ import { MODULE_PATH, PATH_METADATA } from '@nestjs/common/constants'; -import { RouteInfo, Type } from '@nestjs/common/interfaces'; +import { RouteInfo, Type, VersionValue } from '@nestjs/common/interfaces'; import { addLeadingSlash, isString, @@ -58,10 +58,16 @@ export class RoutesMapper { let path = modulePath ?? ''; path += this.normalizeGlobalPath(routePath) + addLeadingSlash(p); - return { + const routeInfo: RouteInfo = { path, method: item.requestMethod, }; + + if (item.version) { + routeInfo.version = item.version; + } + + return routeInfo; }), ) .reduce(concatPaths, []), diff --git a/packages/core/test/middleware/routes-mapper.spec.ts b/packages/core/test/middleware/routes-mapper.spec.ts index 7c1aabd5174..9d31a2e1b9a 100644 --- a/packages/core/test/middleware/routes-mapper.spec.ts +++ b/packages/core/test/middleware/routes-mapper.spec.ts @@ -1,6 +1,10 @@ +import { Version } from '@nestjs/common'; import { expect } from 'chai'; import { Controller } from '../../../common/decorators/core/controller.decorator'; -import { RequestMapping } from '../../../common/decorators/http/request-mapping.decorator'; +import { + Get, + RequestMapping, +} from '../../../common/decorators/http/request-mapping.decorator'; import { RequestMethod } from '../../../common/enums/request-method.enum'; import { NestContainer } from '../../injector/container'; import { RoutesMapper } from '../../middleware/routes-mapper'; @@ -13,6 +17,10 @@ describe('RoutesMapper', () => { @RequestMapping({ path: 'another', method: RequestMethod.DELETE }) public getAnother() {} + + @Version('1') + @Get('versioned') + public getVersioned() {} } let mapper: RoutesMapper; @@ -32,6 +40,7 @@ describe('RoutesMapper', () => { expect(mapper.mapRouteToRouteInfo(config.forRoutes[1])).to.deep.equal([ { path: '/test/test', method: RequestMethod.GET }, { path: '/test/another', method: RequestMethod.DELETE }, + { path: '/test/versioned', method: RequestMethod.GET, version: '1' }, ]); }); @Controller(['test', 'test2']) From 13760346b13e38098d729f1f63db8e62cd5ef30b Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:00:33 -0300 Subject: [PATCH 02/11] feat(core): add version attribute to routes mapper ensure routes mapper returns route's version when defined --- packages/core/middleware/routes-mapper.ts | 53 +++++++++++++------ .../test/middleware/routes-mapper.spec.ts | 16 ++++-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/packages/core/middleware/routes-mapper.ts b/packages/core/middleware/routes-mapper.ts index e32a73096df..5309bb1093a 100644 --- a/packages/core/middleware/routes-mapper.ts +++ b/packages/core/middleware/routes-mapper.ts @@ -22,35 +22,54 @@ export class RoutesMapper { route: Type | RouteInfo | string, ): RouteInfo[] { if (isString(route)) { - const defaultRequestMethod = -1; - return [ - { - path: addLeadingSlash(route), - method: defaultRequestMethod, - }, - ]; + return this.getRouteInfoFromPath(route); } const routePathOrPaths = this.getRoutePath(route); if (this.isRouteInfo(routePathOrPaths, route)) { - return [ - { - path: addLeadingSlash(route.path), - method: route.method, - }, - ]; + return this.getRouteInfoFromObject(route); } + + return this.getRouteInfoFromController(route, routePathOrPaths); + } + + private getRouteInfoFromPath(routePath: string): RouteInfo[] { + const defaultRequestMethod = -1; + return [ + { + path: addLeadingSlash(routePath), + method: defaultRequestMethod, + }, + ]; + } + + private getRouteInfoFromObject(routeInfoObject: RouteInfo): RouteInfo[] { + const routeInfo: RouteInfo = { + path: addLeadingSlash(routeInfoObject.path), + method: routeInfoObject.method, + }; + + if (routeInfoObject.version) { + routeInfo.version = routeInfoObject.version; + } + return [routeInfo]; + } + + private getRouteInfoFromController( + controller: Type, + routePath: string, + ): RouteInfo[] { const controllerPaths = this.routerExplorer.scanForPaths( - Object.create(route), - route.prototype, + Object.create(controller), + controller.prototype, ); - const moduleRef = this.getHostModuleOfController(route); + const moduleRef = this.getHostModuleOfController(controller); const modulePath = this.getModulePath(moduleRef?.metatype); const concatPaths = (acc: T[], currentValue: T[]) => acc.concat(currentValue); return [] - .concat(routePathOrPaths) + .concat(routePath) .map(routePath => controllerPaths .map(item => diff --git a/packages/core/test/middleware/routes-mapper.spec.ts b/packages/core/test/middleware/routes-mapper.spec.ts index 9d31a2e1b9a..f030a676caf 100644 --- a/packages/core/test/middleware/routes-mapper.spec.ts +++ b/packages/core/test/middleware/routes-mapper.spec.ts @@ -1,4 +1,5 @@ -import { Version } from '@nestjs/common'; +import { Version } from '../../../common'; +import { MiddlewareConfiguration } from '../../../common/interfaces'; import { expect } from 'chai'; import { Controller } from '../../../common/decorators/core/controller.decorator'; import { @@ -29,15 +30,24 @@ describe('RoutesMapper', () => { }); it('should map @Controller() to "ControllerMetadata" in forRoutes', () => { - const config = { + const config: MiddlewareConfiguration = { middleware: 'Test', - forRoutes: [{ path: 'test', method: RequestMethod.GET }, TestRoute], + forRoutes: [ + { path: 'test', method: RequestMethod.GET }, + { path: 'versioned', version: '1', method: RequestMethod.GET }, + TestRoute, + ], }; expect(mapper.mapRouteToRouteInfo(config.forRoutes[0])).to.deep.equal([ { path: '/test', method: RequestMethod.GET }, ]); + expect(mapper.mapRouteToRouteInfo(config.forRoutes[1])).to.deep.equal([ + { path: '/versioned', version: '1', method: RequestMethod.GET }, + ]); + + expect(mapper.mapRouteToRouteInfo(config.forRoutes[2])).to.deep.equal([ { path: '/test/test', method: RequestMethod.GET }, { path: '/test/another', method: RequestMethod.DELETE }, { path: '/test/versioned', method: RequestMethod.GET, version: '1' }, From 439652a4e2a6a283a33e845b5b6f5098f225eea4 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:01:20 -0300 Subject: [PATCH 03/11] test(core): ensure middleware builder gets version ensure middleware builder gets route version --- packages/core/test/middleware/builder.spec.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/core/test/middleware/builder.spec.ts b/packages/core/test/middleware/builder.spec.ts index 1cc6e144ded..2ff40229d58 100644 --- a/packages/core/test/middleware/builder.spec.ts +++ b/packages/core/test/middleware/builder.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { Controller, Get } from '../../../common'; +import { Controller, Get, RequestMethod, Version } from '../../../common'; import { NestContainer } from '../../injector/container'; import { MiddlewareBuilder } from '../../middleware/builder'; import { RoutesMapper } from '../../middleware/routes-mapper'; @@ -30,8 +30,12 @@ describe('MiddlewareBuilder', () => { class Test { @Get('route') public getAll() {} + + @Version('1') + @Get('versioned') + public getAllVersioned() {} } - const route = { path: '/test', method: 0 }; + const route = { path: '/test', method: RequestMethod.GET }; it('should store configuration passed as argument', () => { configProxy.forRoutes(route, Test); @@ -40,13 +44,18 @@ describe('MiddlewareBuilder', () => { middleware: [], forRoutes: [ { - method: 0, + method: RequestMethod.GET, path: route.path, }, { - method: 0, + method: RequestMethod.GET, path: '/path/route', }, + { + method: RequestMethod.GET, + path: '/path/versioned', + version: '1', + }, ], }, ]); From bd5c0607f3712b15e433c78fd90f1676740a0cfd Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:02:16 -0300 Subject: [PATCH 04/11] feat(core): add version to middleware module ensure middleware module considers path-versioning when applying middlewares --- .../e2e/middleware-with-versioning.spec.ts | 70 +++++++++++++++++++ packages/core/middleware/middleware-module.ts | 19 ++--- 2 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 integration/hello-world/e2e/middleware-with-versioning.spec.ts diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts new file mode 100644 index 00000000000..bb66451e0f5 --- /dev/null +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -0,0 +1,70 @@ +import { + Controller, + Get, + INestApplication, + MiddlewareConsumer, + Module, + Version, + RequestMethod, + VersioningType, + VERSION_NEUTRAL, +} from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from '../src/app.module'; + +const RETURN_VALUE = 'test'; +const VERSIONED_VALUE = 'test_versioned'; + +@Controller() +class TestController { + @Version('1') + @Get('versioned') + versionedTest() { + return RETURN_VALUE; + } +} + +@Module({ + imports: [AppModule], + controllers: [TestController], +}) +class TestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply((req, res, next) => res.send(VERSIONED_VALUE)) + .forRoutes({ + path: '/versioned', + version: '1', + method: RequestMethod.ALL, + }); + } +} + +describe('Middleware', () => { + let app: INestApplication; + + beforeEach(async () => { + app = ( + await Test.createTestingModule({ + imports: [TestModule], + }).compile() + ).createNestApplication(); + + app.enableVersioning({ + type: VersioningType.URI, + defaultVersion: VERSION_NEUTRAL, + }); + await app.init(); + }); + + it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/v1/versioned') + .expect(200, VERSIONED_VALUE); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 7ba73c5c218..526890df3fe 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -174,8 +174,7 @@ export class MiddlewareModule { await this.bindHandler( instanceWrapper, applicationRef, - routeInfo.method, - routeInfo.path, + routeInfo, moduleRef, collection, ); @@ -185,8 +184,7 @@ export class MiddlewareModule { private async bindHandler( wrapper: InstanceWrapper, applicationRef: HttpServer, - method: RequestMethod, - path: string, + routeInfo: RouteInfo, moduleRef: Module, collection: Map, ) { @@ -197,12 +195,11 @@ export class MiddlewareModule { const isStatic = wrapper.isDependencyTreeStatic(); if (isStatic) { const proxy = await this.createProxy(instance); - return this.registerHandler(applicationRef, method, path, proxy); + return this.registerHandler(applicationRef, routeInfo, proxy); } await this.registerHandler( applicationRef, - method, - path, + routeInfo, async ( req: TRequest, res: TResponse, @@ -266,8 +263,7 @@ export class MiddlewareModule { private async registerHandler( applicationRef: HttpServer, - method: RequestMethod, - path: string, + { path, method, version }: RouteInfo, proxy: ( req: TRequest, res: TResponse, @@ -291,6 +287,11 @@ export class MiddlewareModule { } path = basePath + path; } + + if (version) { + path = `/v${version.toString()}${path}`; + } + const isMethodAll = isRequestMethodAll(method); const requestMethod = RequestMethod[method]; const router = await applicationRef.createMiddlewareFactory(method); From 415858f635e2b4b9d069cf950f55c5bdefc17ea5 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:18:30 -0300 Subject: [PATCH 05/11] test(integration): wrap test case for uri version wrap test case for uri versioning context to facilitate the addition of tests for other versioning types --- .../e2e/middleware-with-versioning.spec.ts | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index bb66451e0f5..968d42cc708 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -8,6 +8,7 @@ import { RequestMethod, VersioningType, VERSION_NEUTRAL, + VersioningOptions, } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; @@ -44,27 +45,37 @@ class TestModule { describe('Middleware', () => { let app: INestApplication; - beforeEach(async () => { - app = ( - await Test.createTestingModule({ - imports: [TestModule], - }).compile() - ).createNestApplication(); - - app.enableVersioning({ - type: VersioningType.URI, - defaultVersion: VERSION_NEUTRAL, + describe('when using URI versioning', () => { + beforeEach(async () => { + app = await createAppWithVersioningType(VersioningType.URI); }); - await app.init(); - }); - it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { - return request(app.getHttpServer()) - .get('/v1/versioned') - .expect(200, VERSIONED_VALUE); + it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/v1/versioned') + .expect(200, VERSIONED_VALUE); + }); }); afterEach(async () => { await app.close(); }); }); + +async function createAppWithVersioningType( + versioningType: VersioningType, +): Promise { + const app = ( + await Test.createTestingModule({ + imports: [TestModule], + }).compile() + ).createNestApplication(); + + app.enableVersioning({ + type: versioningType, + defaultVersion: VERSION_NEUTRAL, + } as VersioningOptions); + await app.init(); + + return app; +} From 4a039327bff13ba2b85cf8b92fc72c19c3c562dd Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:38:54 -0300 Subject: [PATCH 06/11] fix(core): fix middleware for versioned routes fix middlewares for versioned routes using header as versioning type --- .../e2e/middleware-with-versioning.spec.ts | 14 +++++++++++++- packages/core/middleware/middleware-module.ts | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index 968d42cc708..6dff0565657 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -50,13 +50,25 @@ describe('Middleware', () => { app = await createAppWithVersioningType(VersioningType.URI); }); - it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { + it(`forRoutes({ path: ';versioned', version: '1', method: RequestMethod.ALL })`, () => { return request(app.getHttpServer()) .get('/v1/versioned') .expect(200, VERSIONED_VALUE); }); }); + describe('when using HEADER versioning', () => { + beforeEach(async () => { + app = await createAppWithVersioningType(VersioningType.HEADER); + }); + + it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/versioned') + .expect(200, VERSIONED_VALUE); + }); + }); + afterEach(async () => { await app.close(); }); diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 526890df3fe..81a655cdefd 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -1,4 +1,4 @@ -import { HttpServer } from '@nestjs/common'; +import { HttpServer, VersioningType } from '@nestjs/common'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; import { MiddlewareConfiguration, @@ -288,7 +288,7 @@ export class MiddlewareModule { path = basePath + path; } - if (version) { + if (version && this.config.getVersioning().type === VersioningType.URI) { path = `/v${version.toString()}${path}`; } From cf1b946e62d745fae572feac09028681a1da5bd0 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:42:52 -0300 Subject: [PATCH 07/11] fix(core): fix middleware for versioned routes fix middlewares for versioned routes using media type as versioning type --- .../e2e/middleware-with-versioning.spec.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index 6dff0565657..b413d4df1cf 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -50,7 +50,7 @@ describe('Middleware', () => { app = await createAppWithVersioningType(VersioningType.URI); }); - it(`forRoutes({ path: ';versioned', version: '1', method: RequestMethod.ALL })`, () => { + it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { return request(app.getHttpServer()) .get('/v1/versioned') .expect(200, VERSIONED_VALUE); @@ -62,7 +62,19 @@ describe('Middleware', () => { app = await createAppWithVersioningType(VersioningType.HEADER); }); - it(`forRoutes({ path: 'tests/versioned', version: '1', method: RequestMethod.ALL })`, () => { + it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/versioned') + .expect(200, VERSIONED_VALUE); + }); + }); + + describe('when using MEDIA TYPE versioning', () => { + beforeEach(async () => { + app = await createAppWithVersioningType(VersioningType.MEDIA_TYPE); + }); + + it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { return request(app.getHttpServer()) .get('/versioned') .expect(200, VERSIONED_VALUE); From 25f03c4c06cc4ea2f3bad5547f61c0b97a533f38 Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 17:49:13 -0300 Subject: [PATCH 08/11] test(integration): add middleware test --- .../e2e/middleware-with-versioning.spec.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index b413d4df1cf..232d988efbb 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -4,12 +4,13 @@ import { INestApplication, MiddlewareConsumer, Module, - Version, RequestMethod, + Version, + VersioningOptions, VersioningType, VERSION_NEUTRAL, - VersioningOptions, } from '@nestjs/common'; +import { CustomVersioningOptions } from '@nestjs/common/interfaces'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; @@ -81,6 +82,23 @@ describe('Middleware', () => { }); }); + describe('when using CUSTOM TYPE versioning', () => { + beforeEach(async () => { + const extractor: CustomVersioningOptions['extractor'] = () => '1'; + + app = await createAppWithVersioningType( + VersioningType.MEDIA_TYPE, + extractor, + ); + }); + + it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/versioned') + .expect(200, VERSIONED_VALUE); + }); + }); + afterEach(async () => { await app.close(); }); @@ -88,6 +106,7 @@ describe('Middleware', () => { async function createAppWithVersioningType( versioningType: VersioningType, + extractor?: CustomVersioningOptions['extractor'], ): Promise { const app = ( await Test.createTestingModule({ @@ -98,6 +117,7 @@ async function createAppWithVersioningType( app.enableVersioning({ type: versioningType, defaultVersion: VERSION_NEUTRAL, + extractor, } as VersioningOptions); await app.init(); From 092e721263a67f03a8392a1f5f7465d54acdb2cb Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 18:03:36 -0300 Subject: [PATCH 09/11] test(ingration): refactor middleware tests refactor middleware tests to explicitly define versioning options --- .../e2e/middleware-with-versioning.spec.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index 232d988efbb..b15b5a1ec1d 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -48,7 +48,10 @@ describe('Middleware', () => { describe('when using URI versioning', () => { beforeEach(async () => { - app = await createAppWithVersioningType(VersioningType.URI); + app = await createAppWithVersioning({ + type: VersioningType.URI, + defaultVersion: VERSION_NEUTRAL, + }); }); it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { @@ -60,19 +63,27 @@ describe('Middleware', () => { describe('when using HEADER versioning', () => { beforeEach(async () => { - app = await createAppWithVersioningType(VersioningType.HEADER); + app = await createAppWithVersioning({ + type: VersioningType.HEADER, + header: 'version', + }); }); it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { return request(app.getHttpServer()) .get('/versioned') + .set('version', '1') .expect(200, VERSIONED_VALUE); }); }); describe('when using MEDIA TYPE versioning', () => { beforeEach(async () => { - app = await createAppWithVersioningType(VersioningType.MEDIA_TYPE); + app = await createAppWithVersioning({ + type: VersioningType.MEDIA_TYPE, + key: 'v', + defaultVersion: VERSION_NEUTRAL, + }); }); it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { @@ -86,10 +97,10 @@ describe('Middleware', () => { beforeEach(async () => { const extractor: CustomVersioningOptions['extractor'] = () => '1'; - app = await createAppWithVersioningType( - VersioningType.MEDIA_TYPE, + app = await createAppWithVersioning({ + type: VersioningType.CUSTOM, extractor, - ); + }); }); it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { @@ -104,9 +115,8 @@ describe('Middleware', () => { }); }); -async function createAppWithVersioningType( - versioningType: VersioningType, - extractor?: CustomVersioningOptions['extractor'], +async function createAppWithVersioning( + versioningOptions: VersioningOptions, ): Promise { const app = ( await Test.createTestingModule({ @@ -114,11 +124,7 @@ async function createAppWithVersioningType( }).compile() ).createNestApplication(); - app.enableVersioning({ - type: versioningType, - defaultVersion: VERSION_NEUTRAL, - extractor, - } as VersioningOptions); + app.enableVersioning(versioningOptions); await app.init(); return app; From e36acea7834c6c92b7e18663c6d5d08697500dde Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Mon, 31 Oct 2022 18:14:51 -0300 Subject: [PATCH 10/11] feat(core): add middleware versioning support add middleware versionig support for uri versioning with an alternative prefix --- .../e2e/middleware-with-versioning.spec.ts | 18 +++++++++++++++++- packages/core/middleware/middleware-module.ts | 11 +++++++++-- packages/core/nest-application.ts | 4 +++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/integration/hello-world/e2e/middleware-with-versioning.spec.ts b/integration/hello-world/e2e/middleware-with-versioning.spec.ts index b15b5a1ec1d..c8d800a900e 100644 --- a/integration/hello-world/e2e/middleware-with-versioning.spec.ts +++ b/integration/hello-world/e2e/middleware-with-versioning.spec.ts @@ -46,7 +46,7 @@ class TestModule { describe('Middleware', () => { let app: INestApplication; - describe('when using URI versioning', () => { + describe('when using default URI versioning', () => { beforeEach(async () => { app = await createAppWithVersioning({ type: VersioningType.URI, @@ -61,6 +61,22 @@ describe('Middleware', () => { }); }); + describe('when default URI versioning with an alternative prefix', () => { + beforeEach(async () => { + app = await createAppWithVersioning({ + type: VersioningType.URI, + defaultVersion: VERSION_NEUTRAL, + prefix: 'version', + }); + }); + + it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => { + return request(app.getHttpServer()) + .get('/version1/versioned') + .expect(200, VERSIONED_VALUE); + }); + }); + describe('when using HEADER versioning', () => { beforeEach(async () => { app = await createAppWithVersioning({ diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 81a655cdefd..777f589641c 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -20,6 +20,7 @@ import { Injector } from '../injector/injector'; import { InstanceWrapper } from '../injector/instance-wrapper'; import { InstanceToken, Module } from '../injector/module'; import { REQUEST_CONTEXT_ID } from '../router/request/request-constants'; +import { RoutePathFactory } from '../router/route-path-factory'; import { RouterExceptionFilters } from '../router/router-exception-filters'; import { RouterProxy } from '../router/router-proxy'; import { isRequestMethodAll, isRouteExcluded } from '../router/utils'; @@ -40,6 +41,8 @@ export class MiddlewareModule { private container: NestContainer; private httpAdapter: HttpServer; + constructor(private readonly routePathFactory: RoutePathFactory) {} + public async register( middlewareContainer: MiddlewareContainer, container: NestContainer, @@ -288,8 +291,12 @@ export class MiddlewareModule { path = basePath + path; } - if (version && this.config.getVersioning().type === VersioningType.URI) { - path = `/v${version.toString()}${path}`; + const applicationVersioningConfig = this.config.getVersioning(); + if (version && applicationVersioningConfig.type === VersioningType.URI) { + const versionPrefix = this.routePathFactory.getVersionPrefix( + applicationVersioningConfig, + ); + path = `/${versionPrefix}${version.toString()}${path}`; } const isMethodAll = isRequestMethodAll(method); diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index 39a7c3424d5..a06ee4c9837 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -42,6 +42,7 @@ import { MiddlewareModule } from './middleware/middleware-module'; import { NestApplicationContext } from './nest-application-context'; import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface'; import { Resolver } from './router/interfaces/resolver.interface'; +import { RoutePathFactory } from './router/route-path-factory'; import { RoutesResolver } from './router/routes-resolver'; const { SocketModule } = optionalRequire( @@ -63,7 +64,7 @@ export class NestApplication private readonly logger = new Logger(NestApplication.name, { timestamp: true, }); - private readonly middlewareModule = new MiddlewareModule(); + private readonly middlewareModule: MiddlewareModule; private readonly middlewareContainer = new MiddlewareContainer( this.container, ); @@ -85,6 +86,7 @@ export class NestApplication this.selectContextModule(); this.registerHttpServer(); + this.middlewareModule = new MiddlewareModule(new RoutePathFactory(config)); this.routesResolver = new RoutesResolver( this.container, From 4176ee512f960188d2db845fad2ae4b8edeaa7af Mon Sep 17 00:00:00 2001 From: Thiago Martins Date: Tue, 1 Nov 2022 09:53:52 -0300 Subject: [PATCH 11/11] fix(core): add missing parameter for module add missing parameter to middleware module --- packages/core/test/middleware/middleware-module.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/test/middleware/middleware-module.spec.ts b/packages/core/test/middleware/middleware-module.spec.ts index 0b8d44f447a..65e87b6b1d3 100644 --- a/packages/core/test/middleware/middleware-module.spec.ts +++ b/packages/core/test/middleware/middleware-module.spec.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { RoutePathFactory } from '@nestjs/core/router/route-path-factory'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { Controller } from '../../../common/decorators/core/controller.decorator'; @@ -39,7 +40,7 @@ describe('MiddlewareModule', () => { beforeEach(() => { const appConfig = new ApplicationConfig(); - middlewareModule = new MiddlewareModule(); + middlewareModule = new MiddlewareModule(new RoutePathFactory(appConfig)); (middlewareModule as any).routerExceptionFilter = new RouterExceptionFilters( new NestContainer(),