import { RequestMethod } from '@nestjs/common'; import { HttpServer, RouteInfo, Type } from '@nestjs/common/interfaces'; import { addLeadingSlash, isFunction, isString, } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; import * as pathToRegexp from 'path-to-regexp'; import { uid } from 'uid'; import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface'; import { isRouteExcluded } from '../router/utils'; export const mapToExcludeRoute = ( routes: (string | RouteInfo)[], ): ExcludeRouteMetadata[] => { return routes.map(route => { if (isString(route)) { return { path: route, requestMethod: RequestMethod.ALL, pathRegex: pathToRegexp(addLeadingSlash(route)), }; } return { path: route.path, requestMethod: route.method, pathRegex: pathToRegexp(addLeadingSlash(route.path)), }; }); }; export const filterMiddleware = = any>( middleware: T[], routes: RouteInfo[], httpAdapter: HttpServer, ) => { const excludedRoutes = mapToExcludeRoute(routes); return iterate([]) .concat(middleware) .filter(isFunction) .map((item: T) => mapToClass(item, excludedRoutes, httpAdapter)) .toArray(); }; export const mapToClass = >( middleware: T, excludedRoutes: ExcludeRouteMetadata[], httpAdapter: HttpServer, ) => { if (isMiddlewareClass(middleware)) { if (excludedRoutes.length <= 0) { return middleware; } const MiddlewareHost = class extends middleware { use(...params: unknown[]) { const [req, _, next] = params as [Record, any, Function]; const isExcluded = isMiddlewareRouteExcluded( req, excludedRoutes, httpAdapter, ); if (isExcluded) { return next(); } return super.use(...params); } }; return assignToken(MiddlewareHost, middleware.name); } return assignToken( class { use = (...params: unknown[]) => { const [req, _, next] = params as [Record, any, Function]; const isExcluded = isMiddlewareRouteExcluded( req, excludedRoutes, httpAdapter, ); if (isExcluded) { return next(); } return (middleware as Function)(...params); }; }, ); }; export function isMiddlewareClass(middleware: any): middleware is Type { const middlewareStr = middleware.toString(); if (middlewareStr.substring(0, 5) === 'class') { return true; } const middlewareArr = middlewareStr.split(' '); return ( middlewareArr[0] === 'function' && /[A-Z]/.test(middlewareArr[1]?.[0]) && isFunction(middleware.prototype?.use) ); } export function assignToken(metatype: Type, token = uid(21)): Type { Object.defineProperty(metatype, 'name', { value: token }); return metatype; } export function isMiddlewareRouteExcluded( req: Record, excludedRoutes: ExcludeRouteMetadata[], httpAdapter: HttpServer, ): boolean { if (excludedRoutes.length <= 0) { return false; } const reqMethod = httpAdapter.getRequestMethod(req); const originalUrl = httpAdapter.getRequestUrl(req); const queryParamsIndex = originalUrl && originalUrl.indexOf('?'); const pathname = queryParamsIndex >= 0 ? originalUrl.slice(0, queryParamsIndex) : originalUrl; return isRouteExcluded(excludedRoutes, pathname, RequestMethod[reqMethod]); }