From 0e337d0e3a7bb5baca7223dc0ba9b2401ed4872d Mon Sep 17 00:00:00 2001 From: "Micael Levi (@micalevisk)" Date: Tue, 17 May 2022 09:08:28 -0400 Subject: [PATCH 1/2] feat(common,core): make `HttpServer#applyVersionFilter` mandatory --- .../interfaces/http/http-server.interface.ts | 8 +- packages/core/adapters/http-adapter.ts | 9 +- packages/core/router/router-explorer.ts | 127 +-- .../core/test/router/router-explorer.spec.ts | 751 +----------------- packages/core/test/utils/noop-adapter.spec.ts | 12 +- .../adapters/express-adapter.ts | 6 +- .../adapters/fastify-adapter.ts | 13 +- 7 files changed, 56 insertions(+), 870 deletions(-) diff --git a/packages/common/interfaces/http/http-server.interface.ts b/packages/common/interfaces/http/http-server.interface.ts index 46a717d44bd..d2e27de83dd 100644 --- a/packages/common/interfaces/http/http-server.interface.ts +++ b/packages/common/interfaces/http/http-server.interface.ts @@ -74,13 +74,9 @@ export interface HttpServer { close(): any; getType(): string; init?(): Promise; - applyVersionFilter?( + applyVersionFilter( handler: Function, version: VersionValue, versioningOptions: VersioningOptions, - ): = any, TResponse = any>( - req: TRequest, - res: TResponse, - next: () => void, - ) => any; + ): (req: TRequest, res: TResponse, next: () => void) => Function; } diff --git a/packages/core/adapters/http-adapter.ts b/packages/core/adapters/http-adapter.ts index 526afab3890..449c9e68c54 100644 --- a/packages/core/adapters/http-adapter.ts +++ b/packages/core/adapters/http-adapter.ts @@ -1,5 +1,5 @@ -import { HttpServer, RequestMethod } from '@nestjs/common'; -import { RequestHandler } from '@nestjs/common/interfaces'; +import { HttpServer, RequestMethod, VersioningOptions } from '@nestjs/common'; +import { RequestHandler, VersionValue } from '@nestjs/common/interfaces'; import { CorsOptions, CorsOptionsDelegate, @@ -121,4 +121,9 @@ export abstract class AbstractHttpAdapter< | ((path: string, callback: Function) => any) | Promise<(path: string, callback: Function) => any>; abstract getType(): string; + abstract applyVersionFilter( + handler: Function, + version: VersionValue, + versioningOptions: VersioningOptions, + ): (req: TRequest, res: TResponse, next: () => void) => Function; } diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index ab0517dc50b..4a7dd3c39dd 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -334,132 +334,7 @@ export class RouterExplorer { ) { const { versioningOptions } = routePathMetadata; const version = this.routePathFactory.getVersion(routePathMetadata); - if (router?.applyVersionFilter) { - return router.applyVersionFilter(handler, version, versioningOptions); - } - /** - * TODO(v9): This was left for backward-compatibility and can be removed. - */ - return = any, TResponse = any>( - req: TRequest, - res: TResponse, - next: () => void, - ) => { - if (version === VERSION_NEUTRAL) { - return handler(req, res, next); - } - // URL Versioning is done via the path, so the filter continues forward - if (versioningOptions.type === VersioningType.URI) { - return handler(req, res, next); - } - - // Custom Extractor Versioning Handler - if (versioningOptions.type === VersioningType.CUSTOM) { - const extractedVersion = versioningOptions.extractor(req) as - | string - | string[] - | Array; - - if (Array.isArray(version)) { - if ( - Array.isArray(extractedVersion) && - version.filter( - extractedVersion.includes as ( - value: string | symbol, - index: number, - array: Array, - ) => boolean, - ).length - ) { - return handler(req, res, next); - } else if ( - isString(extractedVersion) && - version.includes(extractedVersion) - ) { - return handler(req, res, next); - } - } else { - if ( - Array.isArray(extractedVersion) && - extractedVersion.includes(version) - ) { - return handler(req, res, next); - } else if ( - isString(extractedVersion) && - version === extractedVersion - ) { - return handler(req, res, next); - } - } - } - - // Media Type (Accept Header) Versioning Handler - if (versioningOptions.type === VersioningType.MEDIA_TYPE) { - const MEDIA_TYPE_HEADER = 'Accept'; - const acceptHeaderValue: string | undefined = - req.headers?.[MEDIA_TYPE_HEADER] || - req.headers?.[MEDIA_TYPE_HEADER.toLowerCase()]; - - const acceptHeaderVersionParameter = acceptHeaderValue - ? acceptHeaderValue.split(';')[1] - : undefined; - - // No version was supplied - if (isUndefined(acceptHeaderVersionParameter)) { - if (Array.isArray(version)) { - if (version.includes(VERSION_NEUTRAL)) { - return handler(req, res, next); - } - } - } else { - const headerVersion = acceptHeaderVersionParameter.split( - versioningOptions.key, - )[1]; - - if (Array.isArray(version)) { - if (version.includes(headerVersion)) { - return handler(req, res, next); - } - } else if (isString(version)) { - if (version === headerVersion) { - return handler(req, res, next); - } - } - } - } - // Header Versioning Handler - else if (versioningOptions.type === VersioningType.HEADER) { - const customHeaderVersionParameter: string | undefined = - req.headers?.[versioningOptions.header] || - req.headers?.[versioningOptions.header.toLowerCase()]; - - // No version was supplied - if (isUndefined(customHeaderVersionParameter)) { - if (Array.isArray(version)) { - if (version.includes(VERSION_NEUTRAL)) { - return handler(req, res, next); - } - } - } else { - if (Array.isArray(version)) { - if (version.includes(customHeaderVersionParameter)) { - return handler(req, res, next); - } - } else if (isString(version)) { - if (version === customHeaderVersionParameter) { - return handler(req, res, next); - } - } - } - } - - if (!next) { - throw new InternalServerErrorException( - 'HTTP adapter does not support filtering on version', - ); - } - return next(); - }; + return router.applyVersionFilter(handler, version, versioningOptions); } private createCallbackProxy( diff --git a/packages/core/test/router/router-explorer.spec.ts b/packages/core/test/router/router-explorer.spec.ts index 5f534321007..daeff71f93b 100644 --- a/packages/core/test/router/router-explorer.spec.ts +++ b/packages/core/test/router/router-explorer.spec.ts @@ -1,5 +1,3 @@ -import { VERSION_NEUTRAL } from '@nestjs/common'; -import { VersionValue } from '@nestjs/common/interfaces'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { Controller } from '../../../common/decorators/core/controller.decorator'; @@ -10,7 +8,6 @@ import { } from '../../../common/decorators/http/request-mapping.decorator'; import { RequestMethod } from '../../../common/enums/request-method.enum'; import { VersioningType } from '../../../common/enums/version-type.enum'; -import { VersioningOptions } from '../../../common/interfaces/version-options.interface'; import { Injector } from '../../../core/injector/injector'; import { ApplicationConfig } from '../../application-config'; import { ExecutionContextHost } from '../../helpers/execution-context-host'; @@ -21,6 +18,7 @@ import { RoutePathMetadata } from '../../router/interfaces/route-path-metadata.i import { RoutePathFactory } from '../../router/route-path-factory'; import { RouterExceptionFilters } from '../../router/router-exception-filters'; import { RouterExplorer } from '../../router/router-explorer'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RouterExplorer', () => { @Controller('global') @@ -365,735 +363,34 @@ describe('RouterExplorer', () => { }); describe('applyVersionFilter', () => { - describe('when the versioning type is URI', () => { - describe('and the version is VERSION_NEUTRAL', () => { - it('should return the handler', () => { - const version: VersionValue = VERSION_NEUTRAL; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.URI, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = {}; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - - it('should return the handler', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.URI, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = {}; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - - describe('when the versioning type is MEDIA_TYPE', () => { - it('should return next if there is no Media Type header', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: {} }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return next if there is no version in the Media Type header', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - describe('when the handler version is an array', () => { - describe('and the version has VERSION_NEUTRAL', () => { - it('should return the handler if there is no version in the Media Type header', () => { - const version: VersionValue = [VERSION_NEUTRAL]; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = {}; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - it('should return next if the version in the Media Type header does not match the handler version', () => { - const version: VersionValue = ['1', '2', VERSION_NEUTRAL]; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;v=3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - }); - - it('should return next if the version in the Media Type header does not match the handler version', () => { - const version = ['1', '2']; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;v=3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the Media Type header matches the handler version', () => { - const version = ['1', '2']; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;v=1' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - - describe('when the handler version is a string', () => { - it('should return next if the version in the Media Type header does not match the handler version', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;v=3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the Media Type header matches the handler version', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.MEDIA_TYPE, - key: 'v=', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;v=1' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - }); - - describe('when the versioning type is CUSTOM', () => { - const extractor = (request: { headers: { accept?: string } }) => { - const match = request.headers.accept?.match(/v(\d+\.?\d*)\+json$/); - if (match) { - return match[1]; - } - return null; + it('should call and return the `applyVersionFilter` from the underlying http server', () => { + const router = sinon.spy(new NoopHttpAdapter({})); + const routePathMetadata: RoutePathMetadata = { + methodVersion: + sinon.fake() as unknown as RoutePathMetadata['methodVersion'], + versioningOptions: + sinon.fake() as unknown as RoutePathMetadata['versioningOptions'], }; + const handler = sinon.stub(); - it('should return next if there is no pertinent request object', () => { - const version = '1'; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: {} }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return next if there is no version in the request object value', () => { - const version = '1'; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/json;' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - describe('when the handler version is an array', () => { - it('should return next if the version in the request object value does not match the handler version', () => { - const version = ['1', '2']; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/foo.v3+json' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the request object value matches the handler version', () => { - const version = ['1', '2']; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/foo.v2+json' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - - describe('when the handler version is a string', () => { - it('should return next if the version in the request object value does not match the handler version', () => { - const version = '1'; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/foo.v2+json' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the request object value matches the handler version', () => { - const version = '1'; - const versioningOptions: VersioningOptions = { - type: VersioningType.CUSTOM, - extractor, - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { accept: 'application/foo.v1+json' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - }); - - describe('when the versioning type is HEADER', () => { - it('should return next if there is no Custom Header', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: {} }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return next if there is no version in the Custom Header', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - describe('when the handler version is an array', () => { - describe('and the version has VERSION_NEUTRAL', () => { - it('should return the handler if there is no version in the Custom Header', () => { - const version: VersionValue = [VERSION_NEUTRAL]; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = {}; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - it('should return next if the version in the Custom Header does not match the handler version', () => { - const version: VersionValue = ['1', '2', VERSION_NEUTRAL]; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - }); - - it('should return next if the version in the Custom Header does not match the handler version', () => { - const version = ['1', '2']; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the Custom Header matches the handler version', () => { - const version = ['1', '2']; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '1' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - - describe('when the handler version is a string', () => { - it('should return next if the version in the Custom Header does not match the handler version', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '3' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(next.called).to.be.true; - }); - - it('should return the handler if the version in the Custom Header matches the handler version', () => { - const version = '1'; - const versioningOptions: RoutePathMetadata['versioningOptions'] = { - type: VersioningType.HEADER, - header: 'X-API-Version', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = { headers: { 'X-API-Version': '1' } }; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); - - expect(handler.calledWith(req, res, next)).to.be.true; - }); - }); - }); - - describe('when versioning type is unrecognized', () => { - it('should throw an error if there is no next function', () => { - const version = '1'; - const versioningOptions: any = { - type: 'UNKNOWN', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, - handler, - ); - - const req = {}; - const res = {}; - const next = null; - - expect(() => versionFilter(req, res, next)).to.throw( - 'HTTP adapter does not support filtering on version', - ); - }); + // We're using type assertion here because `applyVersionFilter` is private + const versionFilter = (routerBuilder as any).applyVersionFilter( + router, + routePathMetadata, + handler, + ); - it('should return next', () => { - const version = '1'; - const versioningOptions: any = { - type: 'UNKNOWN', - }; - const handler = sinon.stub(); - - const routePathMetadata: RoutePathMetadata = { - methodVersion: version, - versioningOptions, - }; - const versionFilter = (routerBuilder as any).applyVersionFilter( - null, - routePathMetadata, + expect( + router.applyVersionFilter.calledOnceWithExactly( handler, - ); - - const req = {}; - const res = {}; - const next = sinon.stub(); - - versionFilter(req, res, next); + routePathMetadata.methodVersion, + routePathMetadata.versioningOptions, + ), + ).to.be.true; - expect(next.called).to.be.true; - }); + expect(router.applyVersionFilter.returnValues[0]).to.be.equal( + versionFilter, + ); }); }); }); diff --git a/packages/core/test/utils/noop-adapter.spec.ts b/packages/core/test/utils/noop-adapter.spec.ts index c2e73d09fd6..4935a53eef2 100644 --- a/packages/core/test/utils/noop-adapter.spec.ts +++ b/packages/core/test/utils/noop-adapter.spec.ts @@ -1,4 +1,5 @@ -import { RequestMethod } from '@nestjs/common'; +import { RequestMethod, VersioningOptions } from '@nestjs/common'; +import { VersionValue } from '@nestjs/common/interfaces'; import { AbstractHttpAdapter } from '../../adapters'; export class NoopHttpAdapter extends AbstractHttpAdapter { @@ -25,4 +26,13 @@ export class NoopHttpAdapter extends AbstractHttpAdapter { getType() { return ''; } + applyVersionFilter( + handler: Function, + version: VersionValue, + versioningOptions: VersioningOptions, + ) { + return (req, res, next) => { + return () => {}; + }; + } } diff --git a/packages/platform-express/adapters/express-adapter.ts b/packages/platform-express/adapters/express-adapter.ts index 8cca8922c1e..8c7f1a387b7 100644 --- a/packages/platform-express/adapters/express-adapter.ts +++ b/packages/platform-express/adapters/express-adapter.ts @@ -4,12 +4,10 @@ import { RequestMethod, StreamableFile, VersioningType, -} from '@nestjs/common'; -import { VersioningOptions, - VersionValue, VERSION_NEUTRAL, -} from '@nestjs/common/interfaces'; +} from '@nestjs/common'; +import { VersionValue } from '@nestjs/common/interfaces'; import { CorsOptions, CorsOptionsDelegate, diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 3113a4a29a0..25c61c7a357 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -6,8 +6,9 @@ import { StreamableFile, VersioningOptions, VersioningType, + VERSION_NEUTRAL, } from '@nestjs/common'; -import { VersionValue, VERSION_NEUTRAL } from '@nestjs/common/interfaces'; +import { VersionValue } from '@nestjs/common/interfaces'; import { CorsOptions, CorsOptionsDelegate, @@ -68,7 +69,11 @@ type FastifyHttpsOptions< https: https.ServerOptions; }; -type VersionedRoute = Function & { +type VersionedRoute = (( + req: TRequest, + res: TResponse, + next: Function, +) => Function) & { version: VersionValue; versioningOptions: VersioningOptions; }; @@ -254,11 +259,11 @@ export class FastifyAdapter< handler: Function, version: VersionValue, versioningOptions: VersioningOptions, - ) { + ): VersionedRoute { if (!this.versioningOptions) { this.versioningOptions = versioningOptions; } - const versionedRoute = handler as VersionedRoute; + const versionedRoute = handler as VersionedRoute; versionedRoute.version = version; return versionedRoute; } From 696672e7e6cc42bcdd69335c57c2f4fd4e15b666 Mon Sep 17 00:00:00 2001 From: "Micael Levi (@micalevisk)" Date: Tue, 17 May 2022 20:14:44 -0400 Subject: [PATCH 2/2] refactor(core): remove temp variable on `applyVersionFilter` --- packages/core/router/router-explorer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index 4a7dd3c39dd..6dbbad0ae4e 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -332,9 +332,12 @@ export class RouterExplorer { routePathMetadata: RoutePathMetadata, handler: Function, ) { - const { versioningOptions } = routePathMetadata; const version = this.routePathFactory.getVersion(routePathMetadata); - return router.applyVersionFilter(handler, version, versioningOptions); + return router.applyVersionFilter( + handler, + version, + routePathMetadata.versioningOptions, + ); } private createCallbackProxy(