mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
237 lines
7.3 KiB
TypeScript
237 lines
7.3 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
HttpException,
|
|
NotFoundException,
|
|
} from '@nestjs/common';
|
|
import {
|
|
HOST_METADATA,
|
|
MODULE_PATH,
|
|
VERSION_METADATA,
|
|
} from '@nestjs/common/constants';
|
|
import {
|
|
Controller,
|
|
HttpServer,
|
|
Type,
|
|
VersionValue,
|
|
} from '@nestjs/common/interfaces';
|
|
import { Logger } from '@nestjs/common/services/logger.service';
|
|
import { ApplicationConfig } from '../application-config';
|
|
import {
|
|
CONTROLLER_MAPPING_MESSAGE,
|
|
VERSIONED_CONTROLLER_MAPPING_MESSAGE,
|
|
} from '../helpers/messages';
|
|
import { NestContainer } from '../injector/container';
|
|
import { Injector } from '../injector/injector';
|
|
import { InstanceWrapper } from '../injector/instance-wrapper';
|
|
import { GraphInspector } from '../inspector/graph-inspector';
|
|
import { MetadataScanner } from '../metadata-scanner';
|
|
import { Resolver } from './interfaces/resolver.interface';
|
|
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
|
import { RoutePathFactory } from './route-path-factory';
|
|
import { RouterExceptionFilters } from './router-exception-filters';
|
|
import { RouterExplorer } from './router-explorer';
|
|
import { RouterProxy } from './router-proxy';
|
|
|
|
export class RoutesResolver implements Resolver {
|
|
private readonly logger = new Logger(RoutesResolver.name, {
|
|
timestamp: true,
|
|
});
|
|
private readonly routerProxy = new RouterProxy();
|
|
private readonly routePathFactory: RoutePathFactory;
|
|
private readonly routerExceptionsFilter: RouterExceptionFilters;
|
|
private readonly routerExplorer: RouterExplorer;
|
|
|
|
constructor(
|
|
private readonly container: NestContainer,
|
|
private readonly applicationConfig: ApplicationConfig,
|
|
private readonly injector: Injector,
|
|
graphInspector: GraphInspector,
|
|
) {
|
|
const httpAdapterRef = container.getHttpAdapterRef();
|
|
this.routerExceptionsFilter = new RouterExceptionFilters(
|
|
container,
|
|
applicationConfig,
|
|
httpAdapterRef,
|
|
);
|
|
this.routePathFactory = new RoutePathFactory(this.applicationConfig);
|
|
|
|
const metadataScanner = new MetadataScanner();
|
|
this.routerExplorer = new RouterExplorer(
|
|
metadataScanner,
|
|
this.container,
|
|
this.injector,
|
|
this.routerProxy,
|
|
this.routerExceptionsFilter,
|
|
this.applicationConfig,
|
|
this.routePathFactory,
|
|
graphInspector,
|
|
);
|
|
}
|
|
|
|
public resolve<T extends HttpServer>(
|
|
applicationRef: T,
|
|
globalPrefix: string,
|
|
) {
|
|
const modules = this.container.getModules();
|
|
modules.forEach(({ controllers, metatype }, moduleName) => {
|
|
const modulePath = this.getModulePathMetadata(metatype)!;
|
|
this.registerRouters(
|
|
controllers,
|
|
moduleName,
|
|
globalPrefix,
|
|
modulePath,
|
|
applicationRef,
|
|
);
|
|
});
|
|
}
|
|
|
|
public registerRouters(
|
|
routes: Map<string | symbol | Function, InstanceWrapper<Controller>>,
|
|
moduleName: string,
|
|
globalPrefix: string,
|
|
modulePath: string,
|
|
applicationRef: HttpServer,
|
|
) {
|
|
routes.forEach(instanceWrapper => {
|
|
const { metatype } = instanceWrapper;
|
|
|
|
const host = this.getHostMetadata(metatype!);
|
|
const routerPaths = this.routerExplorer.extractRouterPath(
|
|
metatype as Type<any>,
|
|
);
|
|
const controllerVersion = this.getVersionMetadata(metatype!);
|
|
const controllerName = metatype!.name;
|
|
|
|
routerPaths.forEach(path => {
|
|
const pathsToLog = this.routePathFactory.create({
|
|
ctrlPath: path,
|
|
modulePath,
|
|
globalPrefix,
|
|
});
|
|
if (!controllerVersion) {
|
|
pathsToLog.forEach(path => {
|
|
const logMessage = CONTROLLER_MAPPING_MESSAGE(controllerName, path);
|
|
this.logger.log(logMessage);
|
|
});
|
|
} else {
|
|
pathsToLog.forEach(path => {
|
|
const logMessage = VERSIONED_CONTROLLER_MAPPING_MESSAGE(
|
|
controllerName,
|
|
path,
|
|
controllerVersion,
|
|
);
|
|
this.logger.log(logMessage);
|
|
});
|
|
}
|
|
|
|
const versioningOptions = this.applicationConfig.getVersioning();
|
|
const routePathMetadata: RoutePathMetadata = {
|
|
ctrlPath: path,
|
|
modulePath,
|
|
globalPrefix,
|
|
controllerVersion,
|
|
versioningOptions,
|
|
};
|
|
this.routerExplorer.explore(
|
|
instanceWrapper,
|
|
moduleName,
|
|
applicationRef,
|
|
host!,
|
|
routePathMetadata,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
public registerNotFoundHandler() {
|
|
const applicationRef = this.container.getHttpAdapterRef();
|
|
const callback = <TRequest, TResponse>(req: TRequest, res: TResponse) => {
|
|
const method = applicationRef.getRequestMethod(req);
|
|
const url = applicationRef.getRequestUrl(req);
|
|
throw new NotFoundException(`Cannot ${method} ${url}`);
|
|
};
|
|
const handler = this.routerExceptionsFilter.create({}, callback, undefined);
|
|
const proxy = this.routerProxy.createProxy(callback, handler);
|
|
applicationRef.setNotFoundHandler &&
|
|
applicationRef.setNotFoundHandler(
|
|
proxy,
|
|
this.applicationConfig.getGlobalPrefix(),
|
|
);
|
|
}
|
|
|
|
public registerExceptionHandler() {
|
|
const callback = <TError, TRequest, TResponse>(
|
|
err: TError,
|
|
req: TRequest,
|
|
res: TResponse,
|
|
next: Function,
|
|
) => {
|
|
throw this.mapExternalException(err);
|
|
};
|
|
const handler = this.routerExceptionsFilter.create(
|
|
{},
|
|
callback as any,
|
|
undefined,
|
|
);
|
|
const proxy = this.routerProxy.createExceptionLayerProxy(callback, handler);
|
|
const applicationRef = this.container.getHttpAdapterRef();
|
|
applicationRef.setErrorHandler &&
|
|
applicationRef.setErrorHandler(
|
|
proxy,
|
|
this.applicationConfig.getGlobalPrefix(),
|
|
);
|
|
}
|
|
|
|
public mapExternalException(err: any) {
|
|
switch (true) {
|
|
// SyntaxError is thrown by Express body-parser when given invalid JSON (#422, #430)
|
|
// URIError is thrown by Express when given a path parameter with an invalid percentage
|
|
// encoding, e.g. '%FF' (#8915)
|
|
case err instanceof SyntaxError || err instanceof URIError:
|
|
return new BadRequestException(err.message);
|
|
case this.isHttpFastifyError(err):
|
|
return new HttpException(err.message, err.statusCode);
|
|
default:
|
|
return err;
|
|
}
|
|
}
|
|
|
|
private isHttpFastifyError(
|
|
error: any,
|
|
): error is Error & { statusCode: number } {
|
|
// condition based on this code - https://github.com/fastify/fastify-error/blob/d669b150a82968322f9f7be992b2f6b463272de3/index.js#L22
|
|
return (
|
|
error.statusCode !== undefined &&
|
|
error instanceof Error &&
|
|
error.name === 'FastifyError'
|
|
);
|
|
}
|
|
|
|
private getModulePathMetadata(metatype: Type<unknown>): string | undefined {
|
|
const modulesContainer = this.container.getModules();
|
|
const modulePath = Reflect.getMetadata(
|
|
MODULE_PATH + modulesContainer.applicationId,
|
|
metatype,
|
|
);
|
|
return modulePath ?? Reflect.getMetadata(MODULE_PATH, metatype);
|
|
}
|
|
|
|
private getHostMetadata(
|
|
metatype: Type<unknown> | Function,
|
|
): string | string[] | undefined {
|
|
return Reflect.getMetadata(HOST_METADATA, metatype);
|
|
}
|
|
|
|
private getVersionMetadata(
|
|
metatype: Type<unknown> | Function,
|
|
): VersionValue | undefined {
|
|
const versioningConfig = this.applicationConfig.getVersioning();
|
|
if (versioningConfig) {
|
|
return (
|
|
Reflect.getMetadata(VERSION_METADATA, metatype) ??
|
|
versioningConfig.defaultVersion
|
|
);
|
|
}
|
|
}
|
|
}
|