diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index e7bbc6db1f8..411d86efbda 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -231,6 +231,7 @@ export class RouterExplorer { }, }; + this.copyMetadataToCallback(targetCallback, routeHandler); routerMethodRef(path, routeHandler); this.graphInspector.insertEntrypointDefinition( @@ -422,4 +423,17 @@ export class RouterExplorer { } return contextId; } + + private copyMetadataToCallback( + originalCallback: RouterProxyCallback, + targetCallback: Function, + ) { + for (const key of Reflect.getMetadataKeys(originalCallback)) { + Reflect.defineMetadata( + key, + Reflect.getMetadata(key, originalCallback), + targetCallback, + ); + } + } } diff --git a/packages/core/test/router/router-explorer.spec.ts b/packages/core/test/router/router-explorer.spec.ts index 82ec5f51ffa..cd2220569c3 100644 --- a/packages/core/test/router/router-explorer.spec.ts +++ b/packages/core/test/router/router-explorer.spec.ts @@ -239,4 +239,27 @@ describe('RouterExplorer', () => { ); }); }); + + describe('copyMetadataToCallback', () => { + it('should then copy the metadata from the original callback to the target callback', () => { + const originalCallback = () => {}; + Reflect.defineMetadata( + 'test_metadata_key', + 'test_metadata_value', + originalCallback, + ); + + const targetCallback = () => {}; + + // We're using type assertion here because `copyMetadataToCallback` is private + (routerBuilder as any).copyMetadataToCallback( + originalCallback, + targetCallback, + ); + + expect( + Reflect.getMetadata('test_metadata_key', targetCallback), + ).to.be.equal('test_metadata_value'); + }); + }); }); diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 30ba5e6bbb4..abcb8b81a67 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -52,6 +52,7 @@ import { FastifyStaticOptions, FastifyViewOptions, } from '../interfaces/external'; +import { FASTIFY_ROUTE_CONFIG_METADATA } from '../constants'; type FastifyHttp2SecureOptions< Server extends http2.Http2SecureServer, @@ -261,31 +262,31 @@ export class FastifyAdapter< } public get(...args: any[]) { - return this.injectConstraintsIfVersioned('get', ...args); + return this.injectRouteOptions('get', ...args); } public post(...args: any[]) { - return this.injectConstraintsIfVersioned('post', ...args); + return this.injectRouteOptions('post', ...args); } public head(...args: any[]) { - return this.injectConstraintsIfVersioned('head', ...args); + return this.injectRouteOptions('head', ...args); } public delete(...args: any[]) { - return this.injectConstraintsIfVersioned('delete', ...args); + return this.injectRouteOptions('delete', ...args); } public put(...args: any[]) { - return this.injectConstraintsIfVersioned('put', ...args); + return this.injectRouteOptions('put', ...args); } public patch(...args: any[]) { - return this.injectConstraintsIfVersioned('patch', ...args); + return this.injectRouteOptions('patch', ...args); } public options(...args: any[]) { - return this.injectConstraintsIfVersioned('options', ...args); + return this.injectRouteOptions('options', ...args); } public applyVersionFilter( @@ -638,7 +639,7 @@ export class FastifyAdapter< return rawRequest.originalUrl || rawRequest.url; } - private injectConstraintsIfVersioned( + private injectRouteOptions( routerMethodKey: | 'get' | 'post' @@ -653,14 +654,26 @@ export class FastifyAdapter< const isVersioned = !isUndefined(handlerRef.version) && handlerRef.version !== VERSION_NEUTRAL; + const routeConfig = Reflect.getMetadata( + FASTIFY_ROUTE_CONFIG_METADATA, + handlerRef, + ); + const hasConfig = !isUndefined(routeConfig); - if (isVersioned) { + if (isVersioned || hasConfig) { const isPathAndRouteTuple = args.length === 2; if (isPathAndRouteTuple) { const options = { - constraints: { - version: handlerRef.version, - }, + ...(isVersioned && { + constraints: { + version: handlerRef.version, + }, + }), + ...(hasConfig && { + config: { + ...routeConfig, + }, + }), }; const path = args[0]; return this.instance[routerMethodKey](path, options, handlerRef); diff --git a/packages/platform-fastify/constants.ts b/packages/platform-fastify/constants.ts new file mode 100644 index 00000000000..79929f69ffd --- /dev/null +++ b/packages/platform-fastify/constants.ts @@ -0,0 +1 @@ +export const FASTIFY_ROUTE_CONFIG_METADATA = '__fastify_route_config__'; diff --git a/packages/platform-fastify/decorators/index.ts b/packages/platform-fastify/decorators/index.ts new file mode 100644 index 00000000000..5269ca4a848 --- /dev/null +++ b/packages/platform-fastify/decorators/index.ts @@ -0,0 +1 @@ +export * from './route-config.decorator'; diff --git a/packages/platform-fastify/decorators/route-config.decorator.ts b/packages/platform-fastify/decorators/route-config.decorator.ts new file mode 100644 index 00000000000..6a3f9ad2fc8 --- /dev/null +++ b/packages/platform-fastify/decorators/route-config.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; +import { FASTIFY_ROUTE_CONFIG_METADATA } from '../constants'; + +/** + * @param config See {@link https://fastify.dev/docs/latest/Reference/Routes/#config} + */ +export const RouteConfig = (config: any) => + SetMetadata(FASTIFY_ROUTE_CONFIG_METADATA, config); diff --git a/packages/platform-fastify/index.ts b/packages/platform-fastify/index.ts index 6d28e2ccdbb..6c1cdefb3b8 100644 --- a/packages/platform-fastify/index.ts +++ b/packages/platform-fastify/index.ts @@ -7,3 +7,4 @@ export * from './adapters'; export * from './interfaces'; +export * from './decorators'; diff --git a/packages/platform-fastify/test/decorators/router-config.decorator.spec.ts b/packages/platform-fastify/test/decorators/router-config.decorator.spec.ts new file mode 100644 index 00000000000..3837d04636e --- /dev/null +++ b/packages/platform-fastify/test/decorators/router-config.decorator.spec.ts @@ -0,0 +1,17 @@ +import { expect } from 'chai'; +import { FASTIFY_ROUTE_CONFIG_METADATA } from '../../constants'; +import { RouteConfig } from '../../decorators/route-config.decorator'; + +describe('@RouteConfig', () => { + const routeConfig = { testKey: 'testValue' }; + class Test { + config; + @RouteConfig(routeConfig) + public static test() {} + } + + it('should enhance method with expected fastify route config', () => { + const path = Reflect.getMetadata(FASTIFY_ROUTE_CONFIG_METADATA, Test.test); + expect(path).to.be.eql(routeConfig); + }); +});