mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
fix(platform-fastify): fix versioning (media type and header)
This commit is contained in:
291
integration/versioning/e2e/header-versioning-fastify.spec.ts
Normal file
291
integration/versioning/e2e/header-versioning-fastify.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
291
integration/versioning/e2e/media-type-versioning-fastify.spec.ts
Normal file
291
integration/versioning/e2e/media-type-versioning-fastify.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
162
integration/versioning/e2e/uri-versioning-fastify.spec.ts
Normal file
162
integration/versioning/e2e/uri-versioning-fastify.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
>),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user