From 328169628b28ab89fddb4fb4632a6f249214d134 Mon Sep 17 00:00:00 2001 From: Rychard Vale Date: Mon, 20 Jun 2022 00:58:33 -0300 Subject: [PATCH 1/3] feat(core): middleware runs once for matching route --- .../e2e/middleware-run-match-route.ts | 103 ++++++++++++++++++ packages/core/middleware/builder.ts | 23 +++- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 integration/hello-world/e2e/middleware-run-match-route.ts diff --git a/integration/hello-world/e2e/middleware-run-match-route.ts b/integration/hello-world/e2e/middleware-run-match-route.ts new file mode 100644 index 00000000000..c3c8ed3cfae --- /dev/null +++ b/integration/hello-world/e2e/middleware-run-match-route.ts @@ -0,0 +1,103 @@ +import { + Controller, + Get, + INestApplication, + Injectable, + MiddlewareConsumer, + NestMiddleware, + Module, +} from '@nestjs/common'; +import { Test } from '../../../packages/testing'; +import * as request from 'supertest'; +import { expect } from 'chai'; + +/** + * Number of times that the middleware was executed. + */ +let triggerCounter = 0; +@Injectable() +class Middleware implements NestMiddleware { + use(req, res, next) { + triggerCounter++; + next(); + } +} + +@Controller() +class TestController { + @Get('/test') + testA() {} + + @Get('/:id') + testB() {} + + @Get('/static/route') + testC() {} + + @Get('/:id/:nested') + testD() {} +} + +@Module({ + controllers: [TestController], +}) +class TestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(Middleware).forRoutes(TestController); + } +} + +describe('Middleware (run on route match)', () => { + let app: INestApplication; + + beforeEach(async () => { + triggerCounter = 0; + app = ( + await Test.createTestingModule({ + imports: [TestModule], + }).compile() + ).createNestApplication(); + + await app.init(); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is equal match`, () => { + return request(app.getHttpServer()) + .get('/test') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is not equal match`, () => { + return request(app.getHttpServer()) + .get('/1') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is not of nested params`, () => { + return request(app.getHttpServer()) + .get('/static/route') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + it(`forRoutes(TestController) should execute middleware once when request url is of nested params`, () => { + return request(app.getHttpServer()) + .get('/1/abc') + .expect(200) + .then(() => { + expect(triggerCounter).to.be.eq(1); + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/packages/core/middleware/builder.ts b/packages/core/middleware/builder.ts index d9d63f20f92..078bf2377c7 100644 --- a/packages/core/middleware/builder.ts +++ b/packages/core/middleware/builder.ts @@ -59,7 +59,8 @@ export class MiddlewareBuilder implements MiddlewareConsumer { ): MiddlewareConsumer { const { middlewareCollection } = this.builder; - const forRoutes = this.getRoutesFlatList(routes); + const flattedRoutes = this.getRoutesFlatList(routes); + const forRoutes = this.removeOverlappedRoutes(flattedRoutes); const configuration = { middleware: filterMiddleware( this.middleware, @@ -82,5 +83,25 @@ export class MiddlewareBuilder implements MiddlewareConsumer { .flatten() .toArray(); } + + private removeOverlappedRoutes(routes: RouteInfo[]) { + const regexMatchParams = /(:[^\/]*)/g; + const wildcard = '([^/]*)'; + const routesWithRegex = routes + .filter(route => route.path.indexOf(':') >= 0) + .map(route => ({ + path: route.path, + regex: new RegExp( + '^(' + route.path.replace(regexMatchParams, wildcard) + ')$', + 'g', + ), + })); + return routes.filter(route => { + const routeMatch = routesWithRegex.find( + v => route.path !== v.path && route.path.match(v.regex), + ); + if (routeMatch === undefined) return route; + }); + } }; } From 5bf90ac6d81d43f51a3989f687a37177020553a3 Mon Sep 17 00:00:00 2001 From: Rychard Date: Fri, 24 Jun 2022 23:46:49 -0300 Subject: [PATCH 2/3] fix: increase readability --- packages/core/middleware/builder.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core/middleware/builder.ts b/packages/core/middleware/builder.ts index 078bf2377c7..a229ece92e8 100644 --- a/packages/core/middleware/builder.ts +++ b/packages/core/middleware/builder.ts @@ -88,7 +88,7 @@ export class MiddlewareBuilder implements MiddlewareConsumer { const regexMatchParams = /(:[^\/]*)/g; const wildcard = '([^/]*)'; const routesWithRegex = routes - .filter(route => route.path.indexOf(':') >= 0) + .filter(route => route.path.includes(':')) .map(route => ({ path: route.path, regex: new RegExp( @@ -97,9 +97,11 @@ export class MiddlewareBuilder implements MiddlewareConsumer { ), })); return routes.filter(route => { - const routeMatch = routesWithRegex.find( - v => route.path !== v.path && route.path.match(v.regex), - ); + const isOverlapped = (v: { path: string; regex: RegExp }) => { + return route.path !== v.path && route.path.match(v.regex); + }; + const routeMatch = routesWithRegex.find(isOverlapped); + if (routeMatch === undefined) return route; }); } From b0837af94f08092a78f5db02274792ff77733e51 Mon Sep 17 00:00:00 2001 From: Rychard Vale <54805553+rychardvale@users.noreply.github.com> Date: Sun, 26 Jun 2022 14:57:47 -0300 Subject: [PATCH 3/3] fix: convention/code style. Co-authored-by: Antonio T. as Tony --- packages/core/middleware/builder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/middleware/builder.ts b/packages/core/middleware/builder.ts index a229ece92e8..4aba35d3c46 100644 --- a/packages/core/middleware/builder.ts +++ b/packages/core/middleware/builder.ts @@ -102,7 +102,9 @@ export class MiddlewareBuilder implements MiddlewareConsumer { }; const routeMatch = routesWithRegex.find(isOverlapped); - if (routeMatch === undefined) return route; + if (routeMatch === undefined) { + return route; + } }); } };