fix(platform-fastify): fix versioning (media type and header)

This commit is contained in:
Kamil Myśliwiec
2021-08-17 11:34:48 +02:00
parent 2405b66075
commit 7c394b6110
8 changed files with 1026 additions and 11 deletions

View File

@@ -0,0 +1,291 @@
import { INestApplication, VersioningType } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Versioning (fastify)', () => {
let app: INestApplication;
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
app.enableVersioning({
type: VersioningType.HEADER,
header: 'X-API-Version',
});
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Hello World V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Hello World V2!');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/')
.set({
'X-API-Version': '3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/')
.set({
'X-API-Version': '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /:param', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Parameter V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Parameter V2!');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
'X-API-Version': '3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
'X-API-Version': '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /multiple', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
'X-API-Version': '3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
'X-API-Version': '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/multiple').expect(404);
});
});
describe('GET /neutral', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Neutral');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Neutral');
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
'X-API-Version': '',
})
.expect(200)
.expect('Neutral');
});
it('No Header', () => {
return request(app.getHttpServer())
.get('/neutral')
.expect(200)
.expect('Neutral');
});
});
describe('GET /override', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/override')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Override Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/override')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Override Version 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/override')
.set({
'X-API-Version': '3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/override')
.set({
'X-API-Version': '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/override').expect(404);
});
});
describe('GET /override-partial', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
'X-API-Version': '1',
})
.expect(200)
.expect('Override Partial Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
'X-API-Version': '2',
})
.expect(200)
.expect('Override Partial Version 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
'X-API-Version': '3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
'X-API-Version': '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/override-partial').expect(404);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,291 @@
import { INestApplication, VersioningType } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Versioning (fastify)', () => {
let app: INestApplication;
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
app.enableVersioning({
type: VersioningType.MEDIA_TYPE,
key: 'v=',
});
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Hello World V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Hello World V2!');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/')
.set({
Accept: 'application/json;v=3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/')
.set({
Accept: 'application/json',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /:param', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Parameter V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Parameter V2!');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
Accept: 'application/json;v=3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/param/hello')
.set({
Accept: '',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /multiple', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
Accept: 'application/json;v=3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/multiple')
.set({
Accept: 'application/json',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/multiple').expect(404);
});
});
describe('GET /neutral', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Neutral');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Neutral');
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/neutral')
.set({
Accept: 'application/json',
})
.expect(200)
.expect('Neutral');
});
it('No Header', () => {
return request(app.getHttpServer())
.get('/neutral')
.expect(200)
.expect('Neutral');
});
});
describe('GET /override', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/override')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Override Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/override')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Override Version 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/override')
.set({
Accept: 'application/json;v=3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/override')
.set({
Accept: 'application/json',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/override').expect(404);
});
});
describe('GET /override-partial', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
Accept: 'application/json;v=1',
})
.expect(200)
.expect('Override Partial Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
Accept: 'application/json;v=2',
})
.expect(200)
.expect('Override Partial Version 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
Accept: 'application/json;v=3',
})
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer())
.get('/override-partial')
.set({
Accept: 'application/json',
})
.expect(404);
});
it('No Header', () => {
return request(app.getHttpServer()).get('/override-partial').expect(404);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,162 @@
import { INestApplication, VersioningType } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Versioning (fastify)', () => {
let app: INestApplication;
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
app.enableVersioning({
type: VersioningType.URI,
});
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
describe('GET /', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1')
.expect(200)
.expect('Hello World V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/v2')
.expect(200)
.expect('Hello World V2!');
});
it('V3', () => {
return request(app.getHttpServer()).get('/v3/').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/').expect(404);
});
});
describe('GET /:param', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1/param/hello')
.expect(200)
.expect('Parameter V1!');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/v2/param/hello')
.expect(200)
.expect('Parameter V2!');
});
it('V3', () => {
return request(app.getHttpServer()).get('/v3/param/hello').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/param/hello').expect(404);
});
});
describe('GET /multiple', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1/multiple')
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/v2/multiple')
.expect(200)
.expect('Multiple Versions 1 or 2');
});
it('V3', () => {
return request(app.getHttpServer()).get('/v3/multiple').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/multiple').expect(404);
});
});
describe('GET /neutral', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/neutral')
.expect(200)
.expect('Neutral');
});
});
describe('GET /override', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1/override')
.expect(200)
.expect('Override Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/v2/override')
.expect(200)
.expect('Override Version 2');
});
it('V3', () => {
return request(app.getHttpServer()).get('/v3/override').expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/override').expect(404);
});
});
describe('GET /override-partial', () => {
it('V1', () => {
return request(app.getHttpServer())
.get('/v1/override-partial')
.expect(200)
.expect('Override Partial Version 1');
});
it('V2', () => {
return request(app.getHttpServer())
.get('/v2/override-partial')
.expect(200)
.expect('Override Partial Version 2');
});
it('V3', () => {
return request(app.getHttpServer())
.get('/v3/override-partial')
.expect(404);
});
it('No Version', () => {
return request(app.getHttpServer()).get('/override-partial').expect(404);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -4,6 +4,7 @@ import {
CorsOptionsDelegate,
} from '../../interfaces/external/cors-options.interface';
import { NestApplicationOptions } from '../../interfaces/nest-application-options.interface';
import { VersioningOptions, VersionValue } from '../version-options.interface';
export type ErrorHandler<TRequest = any, TResponse = any> = (
error: any,
@@ -73,4 +74,13 @@ export interface HttpServer<TRequest = any, TResponse = any> {
close(): any;
getType(): string;
init?(): Promise<void>;
applyVersionFilter?(
handler: Function,
version: VersionValue,
versioningOptions: VersioningOptions,
): <TRequest extends Record<string, any> = any, TResponse = any>(
req: TRequest,
res: TResponse,
next: () => void,
) => any;
}

View File

@@ -17,7 +17,7 @@ export abstract class AbstractHttpAdapter<
{
protected httpServer: TServer;
constructor(protected readonly instance: any) {}
constructor(protected instance?: any) {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
public async init() {}
@@ -88,6 +88,10 @@ export abstract class AbstractHttpAdapter<
this.httpServer = httpServer;
}
public setInstance<T = any>(instance: T) {
this.instance = instance;
}
public getInstance<T = any>(): T {
return this.instance as T;
}

View File

@@ -247,7 +247,11 @@ export class RouterExplorer {
routePathMetadata.versioningOptions.type !== VersioningType.URI
) {
// All versioning (except for URI Versioning) is done via the "Version Filter"
routeHandler = this.applyVersionFilter(routePathMetadata, routeHandler);
routeHandler = this.applyVersionFilter(
router,
routePathMetadata,
routeHandler,
);
}
routePathMetadata.methodPath = path;
@@ -323,13 +327,20 @@ export class RouterExplorer {
};
}
private applyVersionFilter(
private applyVersionFilter<T extends HttpServer>(
router: T,
routePathMetadata: RoutePathMetadata,
handler: Function,
) {
const { versioningOptions } = routePathMetadata;
const version = this.routePathFactory.getVersion(routePathMetadata);
if (router?.applyVersionFilter) {
return router.applyVersionFilter(handler, version, versioningOptions);
}
/**
* This can be removed in the next major release.
* Left for backward-compatibility.
*/
return <TRequest extends Record<string, any> = any, TResponse = any>(
req: TRequest,
res: TResponse,

View File

@@ -1,10 +1,25 @@
import { RequestMethod, StreamableFile } from '@nestjs/common';
import {
InternalServerErrorException,
RequestMethod,
StreamableFile,
VersioningType,
} from '@nestjs/common';
import {
VersioningOptions,
VersionValue,
VERSION_NEUTRAL,
} from '@nestjs/common/interfaces';
import {
CorsOptions,
CorsOptionsDelegate,
} from '@nestjs/common/interfaces/external/cors-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { isFunction, isNil, isObject } from '@nestjs/common/utils/shared.utils';
import {
isFunction,
isNil,
isObject,
isString,
} from '@nestjs/common/utils/shared.utils';
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
import * as bodyParser from 'body-parser';
import * as cors from 'cors';
@@ -155,6 +170,78 @@ export class ExpressAdapter extends AbstractHttpAdapter {
return 'express';
}
public applyVersionFilter(
handler: Function,
version: VersionValue,
versioningOptions: VersioningOptions,
) {
return <TRequest extends Record<string, any> = 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);
}
// 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]
: '';
if (acceptHeaderVersionParameter) {
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()];
if (customHeaderVersionParameter) {
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();
};
}
private isMiddlewareApplied(name: string): boolean {
const app = this.getInstance();
return (

View File

@@ -3,7 +3,10 @@ import {
Logger,
RequestMethod,
StreamableFile,
VersioningOptions,
VersioningType,
} from '@nestjs/common';
import { VersionValue, VERSION_NEUTRAL } from '@nestjs/common/interfaces';
import {
CorsOptions,
CorsOptionsDelegate,
@@ -26,6 +29,7 @@ import {
RequestGenericInterface,
} from 'fastify';
import * as Reply from 'fastify/lib/reply';
import { RouteShorthandMethod } from 'fastify/types/route';
import * as http2 from 'http2';
import * as https from 'https';
import {
@@ -61,6 +65,11 @@ type FastifyHttpsOptions<
https: https.ServerOptions;
};
type VersionedRoute = Function & {
version: VersionValue;
versioningOptions: VersioningOptions;
};
/**
* The following type assertion is valid as we enforce "middie" plugin registration
* which enhances the FastifyRequest.RawRequest with the "originalUrl" property.
@@ -91,27 +100,101 @@ export class FastifyAdapter<
> = FastifyInstance<TServer, TRawRequest, TRawResponse>,
> extends AbstractHttpAdapter<TServer, TRequest, TReply> {
protected readonly instance: TInstance;
private _isParserRegistered: boolean;
private isMiddieRegistered: boolean;
private versioningOptions: VersioningOptions;
private readonly versionConstraint = {
name: 'version',
validate(value: unknown) {
if (typeof value !== 'string' && !Array.isArray(value)) {
throw new Error(
'Version constraint should be a string or an array of strings.',
);
}
},
storage() {
const versions = new Map();
return {
get(version: string | Array<string>) {
return versions.get(version) || null;
},
set(
versionOrVersions: string | Array<string>,
store: Map<string, any>,
) {
const storeVersionConstraint = version =>
versions.set(version, store);
if (Array.isArray(versionOrVersions))
versionOrVersions.forEach(storeVersionConstraint);
else storeVersionConstraint(versionOrVersions);
},
del(version: string | Array<string>) {
versions.delete(version);
},
empty() {
versions.clear();
},
};
},
deriveConstraint: (req: FastifyRequest) => {
// Media Type (Accept Header) Versioning Handler
if (this.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()]) as string;
const acceptHeaderVersionParameter = acceptHeaderValue
? acceptHeaderValue.split(';')[1]
: '';
if (acceptHeaderVersionParameter) {
const headerVersion = acceptHeaderVersionParameter.split(
this.versioningOptions.key,
)[1];
return headerVersion;
}
}
// Header Versioning Handler
else if (this.versioningOptions.type === VersioningType.HEADER) {
const customHeaderVersionParameter: string | string[] | undefined =
req.headers?.[this.versioningOptions.header] ||
req.headers?.[this.versioningOptions.header.toLowerCase()];
if (customHeaderVersionParameter) {
return customHeaderVersionParameter;
}
}
return undefined;
},
mustMatchWhenDerived: false,
};
get isParserRegistered(): boolean {
return !!this._isParserRegistered;
}
constructor(
instanceOrOptions:
instanceOrOptions?:
| TInstance
| FastifyHttp2Options<any>
| FastifyHttp2SecureOptions<any>
| FastifyHttpsOptions<any>
| FastifyServerOptions<TServer> = fastify() as any,
| FastifyServerOptions<TServer>,
) {
super();
const instance =
instanceOrOptions && (instanceOrOptions as TInstance).server
? instanceOrOptions
: fastify(instanceOrOptions as FastifyServerOptions);
super(instance);
: fastify({
constraints: {
version: this.versionConstraint as any,
},
...(instanceOrOptions as FastifyServerOptions),
});
this.setInstance(instance);
}
public async init() {
@@ -131,6 +214,47 @@ export class FastifyAdapter<
return this.instance.listen(port, ...args);
}
public get(...args: any[]) {
return this.injectConstraintsIfVersioned('get', ...args);
}
public post(...args: any[]) {
return this.injectConstraintsIfVersioned('post', ...args);
}
public head(...args: any[]) {
return this.injectConstraintsIfVersioned('head', ...args);
}
public delete(...args: any[]) {
return this.injectConstraintsIfVersioned('delete', ...args);
}
public put(...args: any[]) {
return this.injectConstraintsIfVersioned('put', ...args);
}
public patch(...args: any[]) {
return this.injectConstraintsIfVersioned('patch', ...args);
}
public options(...args: any[]) {
return this.injectConstraintsIfVersioned('options', ...args);
}
public applyVersionFilter(
handler: Function,
version: VersionValue,
versioningOptions: VersioningOptions,
) {
if (!this.versioningOptions) {
this.versioningOptions = versioningOptions;
}
const versionedRoute = handler as VersionedRoute;
versionedRoute.version = version;
return versionedRoute;
}
public reply(
response: TRawResponse | TReply,
body: any,
@@ -332,4 +456,39 @@ export class FastifyAdapter<
private getRequestOriginalUrl(rawRequest: TRawRequest) {
return rawRequest.originalUrl || rawRequest.url;
}
private injectConstraintsIfVersioned(
routerMethodKey:
| 'get'
| 'post'
| 'put'
| 'delete'
| 'options'
| 'patch'
| 'head',
...args: any[]
) {
const handlerRef = args[args.length - 1];
const isVersioned =
typeof handlerRef.version !== 'undefined' &&
handlerRef.version !== VERSION_NEUTRAL;
if (isVersioned) {
const isPathAndRouteTuple = args.length === 2;
if (isPathAndRouteTuple) {
const options = {
constraints: {
version: handlerRef.version,
},
};
const path = args[0];
return this.instance[routerMethodKey](path, options, handlerRef);
}
}
return this.instance[routerMethodKey](
...(args as Parameters<
RouteShorthandMethod<TServer, TRawRequest, TRawResponse>
>),
);
}
}