mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
180 lines
5.2 KiB
TypeScript
180 lines
5.2 KiB
TypeScript
import { ApplicationConfig } from '../application-config.js';
|
|
import { NestContainer } from '../injector/container.js';
|
|
import { Module } from '../injector/module.js';
|
|
import { MetadataScanner } from '../metadata-scanner.js';
|
|
import { PathsExplorer, RouteDefinition } from '../router/paths-explorer.js';
|
|
import { targetModulesByContainer } from '../router/router-module.js';
|
|
import {
|
|
MODULE_PATH,
|
|
PATH_METADATA,
|
|
VERSION_METADATA,
|
|
type RouteInfo,
|
|
type VersionValue,
|
|
addLeadingSlash,
|
|
isString,
|
|
isUndefined,
|
|
} from '@nestjs/common/internal';
|
|
import { type Type, VERSION_NEUTRAL } from '@nestjs/common';
|
|
|
|
export class RoutesMapper {
|
|
private readonly pathsExplorer: PathsExplorer;
|
|
|
|
constructor(
|
|
private readonly container: NestContainer,
|
|
private readonly applicationConfig: ApplicationConfig,
|
|
) {
|
|
this.pathsExplorer = new PathsExplorer(new MetadataScanner());
|
|
}
|
|
|
|
public mapRouteToRouteInfo(
|
|
controllerOrRoute: Type<any> | RouteInfo | string,
|
|
): RouteInfo[] {
|
|
if (isString(controllerOrRoute)) {
|
|
return this.getRouteInfoFromPath(controllerOrRoute);
|
|
}
|
|
const routePathOrPaths = this.getRoutePath(controllerOrRoute);
|
|
if (this.isRouteInfo(routePathOrPaths, controllerOrRoute)) {
|
|
return this.getRouteInfoFromObject(controllerOrRoute);
|
|
}
|
|
|
|
return this.getRouteInfoFromController(
|
|
controllerOrRoute,
|
|
routePathOrPaths!,
|
|
);
|
|
}
|
|
|
|
private getRouteInfoFromPath(routePath: string): RouteInfo[] {
|
|
const defaultRequestMethod = -1;
|
|
return [
|
|
{
|
|
path: addLeadingSlash(routePath),
|
|
method: defaultRequestMethod as any,
|
|
},
|
|
];
|
|
}
|
|
|
|
private getRouteInfoFromObject(routeInfoObject: RouteInfo): RouteInfo[] {
|
|
const routeInfo: RouteInfo = {
|
|
path: addLeadingSlash(routeInfoObject.path),
|
|
method: routeInfoObject.method,
|
|
};
|
|
|
|
if (routeInfoObject.version) {
|
|
routeInfo.version = routeInfoObject.version;
|
|
}
|
|
return [routeInfo];
|
|
}
|
|
|
|
private getRouteInfoFromController(
|
|
controller: Type<any>,
|
|
routePath: string,
|
|
): RouteInfo[] {
|
|
const controllerPaths = this.pathsExplorer.scanForPaths(
|
|
Object.create(controller),
|
|
controller.prototype,
|
|
);
|
|
const controllerVersion = this.getVersionMetadata(controller);
|
|
const versioningConfig = this.applicationConfig.getVersioning();
|
|
const moduleRef = this.getHostModuleOfController(controller);
|
|
const modulePath = this.getModulePath(moduleRef?.metatype);
|
|
|
|
const concatPaths = <T>(acc: T[], currentValue: T[]) =>
|
|
acc.concat(currentValue);
|
|
|
|
const toUndefinedIfNeural = (version: VersionValue) =>
|
|
version === VERSION_NEUTRAL ? undefined : version;
|
|
|
|
const toRouteInfo = (item: RouteDefinition, prefix: string) =>
|
|
item.path?.flatMap(p => {
|
|
let endpointPath = modulePath ?? '';
|
|
endpointPath += this.normalizeGlobalPath(prefix) + addLeadingSlash(p);
|
|
|
|
const routeInfo: RouteInfo = {
|
|
path: endpointPath,
|
|
method: item.requestMethod,
|
|
};
|
|
const version = item.version ?? controllerVersion;
|
|
if (version && versioningConfig) {
|
|
if (typeof version !== 'string' && Array.isArray(version)) {
|
|
return version.map(v => ({
|
|
...routeInfo,
|
|
version: toUndefinedIfNeural(v),
|
|
}));
|
|
}
|
|
routeInfo.version = toUndefinedIfNeural(version);
|
|
}
|
|
|
|
return routeInfo;
|
|
});
|
|
|
|
return ([] as string[])
|
|
.concat(routePath)
|
|
.map(routePath =>
|
|
controllerPaths
|
|
.map(item => toRouteInfo(item, routePath))
|
|
.reduce(concatPaths, []),
|
|
)
|
|
.reduce(concatPaths, []);
|
|
}
|
|
|
|
private isRouteInfo(
|
|
path: string | string[] | undefined,
|
|
objectOrClass: Function | RouteInfo,
|
|
): objectOrClass is RouteInfo {
|
|
return isUndefined(path);
|
|
}
|
|
|
|
private normalizeGlobalPath(path: string): string {
|
|
const prefix = addLeadingSlash(path);
|
|
return prefix === '/' ? '' : prefix;
|
|
}
|
|
|
|
private getRoutePath(route: Type<any> | RouteInfo): string | undefined {
|
|
return Reflect.getMetadata(PATH_METADATA, route);
|
|
}
|
|
|
|
private getHostModuleOfController(
|
|
metatype: Type<unknown>,
|
|
): Module | undefined {
|
|
if (!metatype) {
|
|
return;
|
|
}
|
|
const modulesContainer = this.container.getModules();
|
|
const moduleRefsSet = targetModulesByContainer.get(modulesContainer);
|
|
if (!moduleRefsSet) {
|
|
return;
|
|
}
|
|
|
|
const modules = Array.from(modulesContainer.values()).filter(moduleRef =>
|
|
moduleRefsSet.has(moduleRef),
|
|
);
|
|
return modules.find(({ controllers }) => controllers.has(metatype));
|
|
}
|
|
|
|
private getModulePath(
|
|
metatype: Type<unknown> | undefined,
|
|
): string | undefined {
|
|
if (!metatype) {
|
|
return;
|
|
}
|
|
const modulesContainer = this.container.getModules();
|
|
const modulePath = Reflect.getMetadata(
|
|
MODULE_PATH + modulesContainer.applicationId,
|
|
metatype,
|
|
);
|
|
return modulePath ?? Reflect.getMetadata(MODULE_PATH, metatype);
|
|
}
|
|
|
|
private getVersionMetadata(
|
|
metatype: Type<unknown> | Function,
|
|
): VersionValue | undefined {
|
|
const versioningConfig = this.applicationConfig.getVersioning();
|
|
if (versioningConfig) {
|
|
return (
|
|
Reflect.getMetadata(VERSION_METADATA, metatype) ??
|
|
versioningConfig.defaultVersion
|
|
);
|
|
}
|
|
}
|
|
}
|