Skip to content

Commit

Permalink
feat(core): add option to have router module path before version
Browse files Browse the repository at this point in the history
  • Loading branch information
tbedrnik committed Mar 4, 2024
1 parent 7f00840 commit 8a2bd2e
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 23 deletions.
132 changes: 132 additions & 0 deletions integration/versioning/e2e/uri-versioning.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,50 @@ describe('URI Versioning', () => {
});
});

describe('GET /internal-api/internal-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/v2/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/internal-api/v2/internal-resource')
.expect(404);
});
});

describe('GET /public-api/public-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/public-api/public-resource')
.expect(200)
.expect('Public Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/public-api/v2/public-resource')
.expect(200)
.expect('Public Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/v2/public-api/public-resource')
.expect(404);
});
});

after(async () => {
await app.close();
});
Expand Down Expand Up @@ -348,6 +392,50 @@ describe('URI Versioning', () => {
});
});

describe('GET /internal-api/internal-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/v1/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/v2/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/internal-api/v2/internal-resource')
.expect(404);
});
});

describe('GET /public-api/public-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/public-api/v1/public-resource')
.expect(200)
.expect('Public Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/public-api/v2/public-resource')
.expect(200)
.expect('Public Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/v2/public-api/public-resource')
.expect(404);
});
});

after(async () => {
await app.close();
});
Expand Down Expand Up @@ -414,6 +502,50 @@ describe('URI Versioning', () => {
});
});

describe('GET /internal-api/internal-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/api/v1/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/api/v2/internal-api/internal-resource')
.expect(200)
.expect('Internal Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/api/internal-api/v2/internal-resource')
.expect(404);
});
});

describe('GET /public-api/public-resource', () => {
it('No Version', () => {
return request(app.getHttpServer())
.get('/api/public-api/v1/public-resource')
.expect(200)
.expect('Public Resource Version 1');
});

it('V2', () => {
return request(app.getHttpServer())
.get('/api/public-api/v2/public-resource')
.expect(200)
.expect('Public Resource Version 2');
});

it('V2 - wrong version position', () => {
return request(app.getHttpServer())
.get('/api/v2/public-api/public-resource')
.expect(404);
});
});

after(async () => {
await app.close();
});
Expand Down
19 changes: 18 additions & 1 deletion integration/versioning/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,26 @@ import { VersionNeutralController } from './neutral.controller';
import { NoVersioningController } from './no-versioning.controller';
import { OverridePartialController } from './override-partial.controller';
import { OverrideController } from './override.controller';
import { RouterModule } from '@nestjs/core';
import { InternalApiModule } from './internal-api.module';
import { PublicApiModule } from './public-api.module';

@Module({
imports: [],
imports: [
PublicApiModule,
InternalApiModule,
RouterModule.register([
{
path: 'public-api',
pathBeforeVersion: true,
module: PublicApiModule,
},
{
path: 'internal-api',
module: InternalApiModule,
},
]),
],
controllers: [
AppV1Controller,
AppV2Controller,
Expand Down
15 changes: 15 additions & 0 deletions integration/versioning/src/internal-api.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Controller, Get, Version } from '@nestjs/common';

@Controller('internal-resource')
export class InternalApiController {
@Get()
resource() {
return 'Internal Resource Version 1';
}

@Version('2')
@Get()
resourceV2() {
return 'Internal Resource Version 2';
}
}
7 changes: 7 additions & 0 deletions integration/versioning/src/internal-api.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { InternalApiController } from './internal-api.controller';

@Module({
controllers: [InternalApiController],
})
export class InternalApiModule {}
15 changes: 15 additions & 0 deletions integration/versioning/src/public-api.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Controller, Get, Version } from '@nestjs/common';

@Controller('public-resource')
export class PublicApiController {
@Get()
resource() {
return 'Public Resource Version 1';
}

@Version('2')
@Get()
resourceV2() {
return 'Public Resource Version 2';
}
}
7 changes: 7 additions & 0 deletions integration/versioning/src/public-api.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { PublicApiController } from './public-api.controller';

@Module({
controllers: [PublicApiController],
})
export class PublicApiModule {}
1 change: 1 addition & 0 deletions packages/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type EnhancerSubtype =
export const RENDER_METADATA = '__renderTemplate__';
export const HTTP_CODE_METADATA = '__httpCode__';
export const MODULE_PATH = '__module_path__';
export const MODULE_PATH_BEFORE_VERSION = '__modulePathBeforeVersion__';
export const HEADERS_METADATA = '__headers__';
export const REDIRECT_METADATA = '__redirect__';
export const RESPONSE_PASSTHROUGH_METADATA = '__responsePassthrough__';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export interface RoutePathMetadata {
*/
modulePath?: string;

/**
* Option to put the route version after the module-level path.
*/
modulePathBeforeVersion?: boolean;

/**
* Controller-level version (e.g., @Controller({ version: '1.0' }) = "1.0").
*/
Expand Down
1 change: 1 addition & 0 deletions packages/core/router/interfaces/routes.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Type } from '@nestjs/common';

export interface RouteTree {
path: string;
pathBeforeVersion?: boolean;
module?: Type<any>;
children?: (RouteTree | Type<any>)[];
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/router/route-path-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export class RoutePathFactory {
): string[] {
let paths = [''];

if (metadata.modulePathBeforeVersion) {
paths = this.appendToAllIfDefined(paths, metadata.modulePath);
}

const versionOrVersions = this.getVersion(metadata);
if (
versionOrVersions &&
Expand Down Expand Up @@ -52,7 +56,9 @@ export class RoutePathFactory {
}
}

paths = this.appendToAllIfDefined(paths, metadata.modulePath);
if (!metadata.modulePathBeforeVersion) {
paths = this.appendToAllIfDefined(paths, metadata.modulePath);
}
paths = this.appendToAllIfDefined(paths, metadata.ctrlPath);
paths = this.appendToAllIfDefined(paths, metadata.methodPath);

Expand Down
20 changes: 19 additions & 1 deletion packages/core/router/router-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { DynamicModule, Inject, Module, Type } from '@nestjs/common';
import { MODULE_PATH } from '@nestjs/common/constants';
import {
MODULE_PATH,
MODULE_PATH_BEFORE_VERSION,
} from '@nestjs/common/constants';
import { normalizePath } from '@nestjs/common/utils/shared.utils';
import { Module as ModuleClass } from '../injector/module';
import { ModulesContainer } from '../injector/modules-container';
Expand Down Expand Up @@ -60,6 +63,10 @@ export class RouterModule {
flattenedRoutes.forEach(route => {
const modulePath = normalizePath(route.path);
this.registerModulePathMetadata(route.module, modulePath);
this.registerModulePathBeforeVersionMetadata(
route.module,
route.pathBeforeVersion,
);
this.updateTargetModulesCache(route.module);
});
}
Expand All @@ -75,6 +82,17 @@ export class RouterModule {
);
}

private registerModulePathBeforeVersionMetadata(
moduleCtor: Type<unknown>,
modulePathBeforeVersion = false,
) {
Reflect.defineMetadata(
MODULE_PATH_BEFORE_VERSION + this.modulesContainer.applicationId,
modulePathBeforeVersion,
moduleCtor,
);
}

private updateTargetModulesCache(moduleCtor: Type<unknown>) {
let moduleClassSet: WeakSet<ModuleClass>;
if (targetModulesByContainer.has(this.modulesContainer)) {
Expand Down
21 changes: 21 additions & 0 deletions packages/core/router/routes-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BadRequestException, NotFoundException } from '@nestjs/common';
import {
HOST_METADATA,
MODULE_PATH,
MODULE_PATH_BEFORE_VERSION,
VERSION_METADATA,
} from '@nestjs/common/constants';
import {
Expand Down Expand Up @@ -71,11 +72,14 @@ export class RoutesResolver implements Resolver {
const modules = this.container.getModules();
modules.forEach(({ controllers, metatype }, moduleName) => {
const modulePath = this.getModulePathMetadata(metatype);
const modulePathBeforeVersion =
this.getModulePathBeforeVersionMetadata(metatype);
this.registerRouters(
controllers,
moduleName,
globalPrefix,
modulePath,
modulePathBeforeVersion,
applicationRef,
);
});
Expand All @@ -86,6 +90,7 @@ export class RoutesResolver implements Resolver {
moduleName: string,
globalPrefix: string,
modulePath: string,
modulePathBeforeVersion: boolean,
applicationRef: HttpServer,
) {
routes.forEach(instanceWrapper => {
Expand All @@ -102,6 +107,7 @@ export class RoutesResolver implements Resolver {
const pathsToLog = this.routePathFactory.create({
ctrlPath: path,
modulePath,
modulePathBeforeVersion,
globalPrefix,
});
if (!controllerVersion) {
Expand All @@ -124,6 +130,7 @@ export class RoutesResolver implements Resolver {
const routePathMetadata: RoutePathMetadata = {
ctrlPath: path,
modulePath,
modulePathBeforeVersion,
globalPrefix,
controllerVersion,
versioningOptions,
Expand Down Expand Up @@ -199,6 +206,20 @@ export class RoutesResolver implements Resolver {
return modulePath ?? Reflect.getMetadata(MODULE_PATH, metatype);
}

private getModulePathBeforeVersionMetadata(
metatype: Type<unknown>,
): boolean | undefined {
const modulesContainer = this.container.getModules();
const modulePathBeforeVersion = Reflect.getMetadata(
MODULE_PATH_BEFORE_VERSION + modulesContainer.applicationId,
metatype,
);
return (
modulePathBeforeVersion ??
Reflect.getMetadata(MODULE_PATH_BEFORE_VERSION, metatype)
);
}

private getHostMetadata(
metatype: Type<unknown> | Function,
): string | string[] | undefined {
Expand Down
Loading

0 comments on commit 8a2bd2e

Please sign in to comment.