From 82892728615b1b06b4d771c92585ebada8d67c64 Mon Sep 17 00:00:00 2001 From: Zclhlmgqzc <502950670@qq.com> Date: Mon, 4 Oct 2021 19:33:00 +0800 Subject: [PATCH] fix(core): middlewareConsumer forRoutes controller --- .../hello-world/e2e/middleware-class.spec.ts | 82 +++++++++++- .../e2e/middleware-fastify.spec.ts | 124 +++++++++++++++++- packages/core/middleware/routes-mapper.ts | 3 +- packages/core/test/middleware/builder.spec.ts | 2 +- .../test/middleware/routes-mapper.spec.ts | 12 +- .../adapters/fastify-adapter.ts | 17 ++- 6 files changed, 220 insertions(+), 20 deletions(-) diff --git a/integration/hello-world/e2e/middleware-class.spec.ts b/integration/hello-world/e2e/middleware-class.spec.ts index 13422261180..61c80b80656 100644 --- a/integration/hello-world/e2e/middleware-class.spec.ts +++ b/integration/hello-world/e2e/middleware-class.spec.ts @@ -5,6 +5,7 @@ import { Injectable, MiddlewareConsumer, Module, + Param, RequestMethod, } from '@nestjs/common'; import { Test } from '@nestjs/testing'; @@ -12,6 +13,9 @@ import * as request from 'supertest'; import { ApplicationModule } from '../src/app.module'; import { Response } from 'express'; +const OPTIONAL_PARAM_VALUE = 'test_optional_param'; +const FOR_ROUTE_CONTROLLER_VALUE = 'test_for_route_controller'; +const FOR_ROUTE_PATH_VALUE = 'test_for_route_path'; const INCLUDED_VALUE = 'test_included'; const RETURN_VALUE = 'test'; const WILDCARD_VALUE = 'test_wildcard'; @@ -31,16 +35,44 @@ class TestController { } } +@Controller(OPTIONAL_PARAM_VALUE) +class TestParamController { + @Get(':test') + [OPTIONAL_PARAM_VALUE]() { + return RETURN_VALUE; + } +} + +@Controller() +class ForRouteController { + @Get('for_route_controller') + forRouteController() { + return RETURN_VALUE; + } + + @Get('for_route_controller/required_param/:param') + requiredParam() { + return RETURN_VALUE; + } +} + @Module({ imports: [ApplicationModule], - controllers: [TestController], + controllers: [TestController, TestParamController, ForRouteController], }) class TestModule { configure(consumer: MiddlewareConsumer) { consumer .apply((req, res: Response, next) => res.status(201).end(INCLUDED_VALUE)) .forRoutes({ path: 'tests/included', method: RequestMethod.POST }) + .apply((req, res: Response, next) => res.end(FOR_ROUTE_PATH_VALUE)) + .forRoutes({ path: 'for_route_path', method: RequestMethod.GET }) + .apply((req, res: Response, next) => res.end(FOR_ROUTE_CONTROLLER_VALUE)) + .forRoutes(ForRouteController) + .apply((req, res, next) => res.end(OPTIONAL_PARAM_VALUE)) + .forRoutes({ path: `${OPTIONAL_PARAM_VALUE}/(:test)?`, method: RequestMethod.GET }) .apply(Middleware) + .exclude({ path: `${OPTIONAL_PARAM_VALUE}/(.*)`, method: RequestMethod.ALL }) .forRoutes('*'); } } @@ -82,6 +114,54 @@ describe('Middleware (class)', () => { .expect(201, INCLUDED_VALUE); }); + it(`/for_route_path forRoutes(/for_route_path)`, () => { + return request(app.getHttpServer()) + .get('/for_route_path') + .expect(200, FOR_ROUTE_PATH_VALUE); + }); + + it(`/for_route_path/test forRoutes(/for_route_path)`, () => { + return request(app.getHttpServer()) + .get('/for_route_path/test') + .expect(200, FOR_ROUTE_PATH_VALUE); + }); + + it(`/for_route_controller forRoutes(ForRouteController)`, () => { + return request(app.getHttpServer()) + .get('/for_route_controller') + .expect(200, FOR_ROUTE_CONTROLLER_VALUE); + }); + + it(`/for_route_controller/test forRoutes(ForRouteController)`, () => { + return request(app.getHttpServer()) + .get('/for_route_controller/test') + .expect(200, WILDCARD_VALUE); + }); + + it(`/for_route_controller/required_param/ forRoutes(ForRouteController)`, () => { + return request(app.getHttpServer()) + .get('/for_route_controller/required_param/') + .expect(200, WILDCARD_VALUE); + }); + + it(`/for_route_controller/required_param/test forRoutes(ForRouteController)`, () => { + return request(app.getHttpServer()) + .get('/for_route_controller/required_param/test') + .expect(200, FOR_ROUTE_CONTROLLER_VALUE); + }); + + it(`/${OPTIONAL_PARAM_VALUE}/ forRoutes(${OPTIONAL_PARAM_VALUE}/(:test)?)`, () => { + return request(app.getHttpServer()) + .get(`/${OPTIONAL_PARAM_VALUE}/`) + .expect(200, OPTIONAL_PARAM_VALUE); + }); + + it(`/${OPTIONAL_PARAM_VALUE}/test forRoutes(${OPTIONAL_PARAM_VALUE}/(:test)?)`, () => { + return request(app.getHttpServer()) + .get(`/${OPTIONAL_PARAM_VALUE}/test`) + .expect(200, OPTIONAL_PARAM_VALUE); + }); + afterEach(async () => { await app.close(); }); diff --git a/integration/hello-world/e2e/middleware-fastify.spec.ts b/integration/hello-world/e2e/middleware-fastify.spec.ts index 4fe34a0b3df..f5170b4bcf5 100644 --- a/integration/hello-world/e2e/middleware-fastify.spec.ts +++ b/integration/hello-world/e2e/middleware-fastify.spec.ts @@ -3,6 +3,7 @@ import { Get, MiddlewareConsumer, Module, + Param, Query, RequestMethod, } from '@nestjs/common'; @@ -14,12 +15,16 @@ import { Test } from '@nestjs/testing'; import { expect } from 'chai'; import { ApplicationModule } from '../src/app.module'; +const OPTIONAL_PARAM_VALUE = 'test_optional_param'; +const FOR_ROUTE_CONTROLLER_VALUE = 'test_for_route_controller'; +const FOR_ROUTE_PATH_VALUE = 'test_for_route_path'; const INCLUDED_VALUE = 'test_included'; const QUERY_VALUE = 'test_query'; const REQ_URL_VALUE = 'test_req_url'; const RETURN_VALUE = 'test'; const SCOPED_VALUE = 'test_scoped'; const WILDCARD_VALUE = 'test_wildcard'; +const CATCH_ALL_VALUE = 'test_catch_all'; @Controller() class TestController { @@ -57,9 +62,35 @@ class TestQueryController { } } +@Controller(OPTIONAL_PARAM_VALUE) +class TestParamController { + @Get(':test') + [OPTIONAL_PARAM_VALUE]() { + return RETURN_VALUE; + } +} + +@Controller() +class ForRouteController { + @Get('for_route_controller') + forRouteController() { + return RETURN_VALUE; + } + + @Get('for_route_controller/required_param/:param') + requiredParam() { + return RETURN_VALUE; + } +} + @Module({ imports: [ApplicationModule], - controllers: [TestController, TestQueryController], + controllers: [ + TestController, + TestQueryController, + TestParamController, + ForRouteController, + ], }) class TestModule { configure(consumer: MiddlewareConsumer) { @@ -76,8 +107,17 @@ class TestModule { .forRoutes(TestQueryController) .apply((req, res, next) => res.end(SCOPED_VALUE)) .forRoutes(TestController) - .apply((req, res, next) => res.end(RETURN_VALUE)) - .exclude({ path: QUERY_VALUE, method: -1 }) + .apply((req, res, next) => res.end(FOR_ROUTE_PATH_VALUE)) + .forRoutes({ path: 'for_route_path', method: RequestMethod.GET }) + .apply((req, res, next) => res.end(FOR_ROUTE_CONTROLLER_VALUE)) + .forRoutes(ForRouteController) + .apply((req, res, next) => res.end(OPTIONAL_PARAM_VALUE)) + .forRoutes({ path: `${OPTIONAL_PARAM_VALUE}/:test?`, method: RequestMethod.GET }) + .apply((req, res, next) => res.end(CATCH_ALL_VALUE)) + .exclude( + { path: QUERY_VALUE, method: RequestMethod.ALL }, + { path: `${OPTIONAL_PARAM_VALUE}/(.*)`, method: RequestMethod.ALL }, + ) .forRoutes('(.*)'); } } @@ -101,7 +141,7 @@ describe('Middleware (FastifyAdapter)', () => { method: 'GET', url: '/hello', }) - .then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE)); + .then(({ payload }) => expect(payload).to.be.eql(CATCH_ALL_VALUE)); }); it(`forRoutes(TestController)`, () => { @@ -185,6 +225,82 @@ describe('Middleware (FastifyAdapter)', () => { .then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE)); }); + it(`/for_route_path forRoutes(/for_route_path)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_path', + }) + .then(({ payload }) => expect(payload).to.be.eql(FOR_ROUTE_PATH_VALUE)); + }); + + it(`/for_route_path/test forRoutes(/for_route_path)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_path/test', + }) + .then(({ payload }) => expect(payload).to.be.eql(FOR_ROUTE_PATH_VALUE)); + }); + + it(`/for_route_controller forRoutes(ForRouteController)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_controller', + }) + .then(({ payload }) => + expect(payload).to.be.eql(FOR_ROUTE_CONTROLLER_VALUE), + ); + }); + + it(`/for_route_controller/test forRoutes(ForRouteController)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_controller/test', + }) + .then(({ payload }) => expect(payload).to.be.eql(CATCH_ALL_VALUE)); + }); + + it(`/for_route_controller/required_param/ forRoutes(ForRouteController)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_controller/required_param/', + }) + .then(({ payload }) => expect(payload).to.be.eql(CATCH_ALL_VALUE)); + }); + + it(`/for_route_controller/required_param/test forRoutes(ForRouteController)`, () => { + return app + .inject({ + method: 'GET', + url: '/for_route_controller/required_param/test', + }) + .then(({ payload }) => + expect(payload).to.be.eql(FOR_ROUTE_CONTROLLER_VALUE), + ); + }); + + it(`/${OPTIONAL_PARAM_VALUE}/ forRoutes(${OPTIONAL_PARAM_VALUE}/:test?)`, () => { + return app + .inject({ + method: 'GET', + url: `/${OPTIONAL_PARAM_VALUE}/`, + }) + .then(({ payload }) => expect(payload).to.be.eql(OPTIONAL_PARAM_VALUE)); + }); + + it(`/${OPTIONAL_PARAM_VALUE}/test forRoutes(${OPTIONAL_PARAM_VALUE}/:test?)`, () => { + return app + .inject({ + method: 'GET', + url: `/${OPTIONAL_PARAM_VALUE}/test`, + }) + .then(({ payload }) => expect(payload).to.be.eql(OPTIONAL_PARAM_VALUE)); + }); + afterEach(async () => { await app.close(); }); diff --git a/packages/core/middleware/routes-mapper.ts b/packages/core/middleware/routes-mapper.ts index bbb8ea36cf2..cb6696b0f36 100644 --- a/packages/core/middleware/routes-mapper.ts +++ b/packages/core/middleware/routes-mapper.ts @@ -56,7 +56,8 @@ export class RoutesMapper { .map(item => item.path?.map(p => { let path = modulePath ?? ''; - path += this.normalizeGlobalPath(routePath) + addLeadingSlash(p); + path += + this.normalizeGlobalPath(routePath) + addLeadingSlash(p) + '$'; return { path, diff --git a/packages/core/test/middleware/builder.spec.ts b/packages/core/test/middleware/builder.spec.ts index 1cc6e144ded..036868c92b0 100644 --- a/packages/core/test/middleware/builder.spec.ts +++ b/packages/core/test/middleware/builder.spec.ts @@ -45,7 +45,7 @@ describe('MiddlewareBuilder', () => { }, { method: 0, - path: '/path/route', + path: '/path/route$', }, ], }, diff --git a/packages/core/test/middleware/routes-mapper.spec.ts b/packages/core/test/middleware/routes-mapper.spec.ts index 7c1aabd5174..9a700e08d91 100644 --- a/packages/core/test/middleware/routes-mapper.spec.ts +++ b/packages/core/test/middleware/routes-mapper.spec.ts @@ -30,8 +30,8 @@ describe('RoutesMapper', () => { { path: '/test', method: RequestMethod.GET }, ]); expect(mapper.mapRouteToRouteInfo(config.forRoutes[1])).to.deep.equal([ - { path: '/test/test', method: RequestMethod.GET }, - { path: '/test/another', method: RequestMethod.DELETE }, + { path: '/test/test$', method: RequestMethod.GET }, + { path: '/test/another$', method: RequestMethod.DELETE }, ]); }); @Controller(['test', 'test2']) @@ -56,10 +56,10 @@ describe('RoutesMapper', () => { { path: '/test', method: RequestMethod.GET }, ]); expect(mapper.mapRouteToRouteInfo(config.forRoutes[1])).to.deep.equal([ - { path: '/test/test', method: RequestMethod.GET }, - { path: '/test/another', method: RequestMethod.DELETE }, - { path: '/test2/test', method: RequestMethod.GET }, - { path: '/test2/another', method: RequestMethod.DELETE }, + { path: '/test/test$', method: RequestMethod.GET }, + { path: '/test/another$', method: RequestMethod.DELETE }, + { path: '/test2/test$', method: RequestMethod.GET }, + { path: '/test2/another$', method: RequestMethod.DELETE }, ]); }); }); diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 0c393abb159..5a9c06e78a0 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -37,6 +37,7 @@ import { InjectOptions, Response as LightMyRequestResponse, } from 'light-my-request'; +import * as pathToRegexp from 'path-to-regexp'; import { FastifyStaticOptions, PointOfViewOptions, @@ -414,16 +415,18 @@ export class FastifyAdapter< await this.registerMiddie(); } return (path: string, callback: Function) => { - const normalizedPath = path.endsWith('/*') - ? `${path.slice(0, -1)}(.*)` - : path; + if (path.endsWith('/*')) { + path = `${path.slice(0, -1)}(.*)` + } else if (path.endsWith('$')) { + path = pathToRegexp(path.slice(0, -1), [], { + end: true, + strict: false + }) as any + } // The following type assertion is valid as we use import('middie') rather than require('middie') // ref https://github.com/fastify/middie/pull/55 - this.instance.use( - normalizedPath, - callback as Parameters['1'], - ); + this.instance.use(path, callback as Parameters['1']); }; }