All files / core/middleware builder.ts

95.24% Statements 40/42
87.5% Branches 7/8
100% Functions 16/16
95.12% Lines 39/41
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 1151x 1x             1x 1x   1x   1x 11x   11x         9x       2x             1x 1x           1x 9x 9x     9x 9x       1x       1x 1x           6x 6x 6x   6x                   1x   1x 2x   1x         2x   1x 1x       7x       7x   7x       7x 5x 5x 1x   4x                
import { RequestMethod } from '@nestjs/common';
import { flatten } from '@nestjs/common/decorators/core/dependencies.decorator';
import { MiddlewareConsumer, Type } from '@nestjs/common/interfaces';
import {
  MiddlewareConfigProxy,
  RouteInfo,
} from '@nestjs/common/interfaces/middleware';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { BindResolveMiddlewareValues } from '@nestjs/common/utils/bind-resolve-values.util';
import { isNil } from '@nestjs/common/utils/shared.utils';
import { RoutesMapper } from './routes-mapper';
import { filterMiddleware } from './utils';
 
export class MiddlewareBuilder implements MiddlewareConsumer {
  private readonly middlewareCollection = new Set<MiddlewareConfiguration>();
 
  constructor(private readonly routesMapper: RoutesMapper) {}
 
  public apply(
    ...middleware: Array<Type<any> | Function | any>
  ): MiddlewareConfigProxy {
    return new MiddlewareBuilder.ConfigProxy(this, flatten(middleware));
  }
 
  public build() {
    return [...this.middlewareCollection];
  }
 
  private bindValuesToResolve(
    middleware: Type<any> | Type<any>[],
    resolveParams: any[],
  ) {
    Eif (isNil(resolveParams)) {
      return middleware;
    }
    const bindArgs = BindResolveMiddlewareValues(resolveParams);
    return [].concat(middleware).map(bindArgs);
  }
 
  private static readonly ConfigProxy = class implements MiddlewareConfigProxy {
    private contextParameters = null;
    private excludedRoutes: RouteInfo[] = [];
    private readonly includedRoutes: any[];
 
    constructor(private readonly builder: MiddlewareBuilder, middleware) {
      this.includedRoutes = filterMiddleware(middleware);
    }
 
    public getExcludedRoutes(): RouteInfo[] {
      return this.excludedRoutes;
    }
 
    public with(...args): MiddlewareConfigProxy {
      this.contextParameters = args;
      return this;
    }
 
    public exclude(
      ...routes: Array<string | RouteInfo>
    ): MiddlewareConfigProxy {
      const { routesMapper } = this.builder;
      this.excludedRoutes = this.mapRoutesToFlatList(
        routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
      );
      return this;
    }
 
    public forRoutes(
      ...routes: Array<string | Type<any> | RouteInfo>
    ): MiddlewareConsumer {
      const {
        middlewareCollection,
        bindValuesToResolve,
        routesMapper,
      } = this.builder;
 
      const forRoutes = this.mapRoutesToFlatList(
        routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
      );
      const configuration = {
        middleware: bindValuesToResolve(
          this.includedRoutes,
          this.contextParameters,
        ),
        forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
      };
      middlewareCollection.add(configuration);
      return this.builder;
    }
 
    private mapRoutesToFlatList(forRoutes): RouteInfo[] {
      return forRoutes.reduce((a, b) => a.concat(b));
    }
 
    private isRouteExcluded(routeInfo: RouteInfo): boolean {
      const pathLastIndex = routeInfo.path.length - 1;
      const validatedRoutePath =
        routeInfo.path[pathLastIndex] === '/'
          ? routeInfo.path.slice(0, pathLastIndex)
          : routeInfo.path;
 
      return this.excludedRoutes.some(excluded => {
        const isPathEqual = validatedRoutePath === excluded.path;
        if (!isPathEqual) {
          return false;
        }
        return (
          routeInfo.method === excluded.method ||
          excluded.method === RequestMethod.ALL
        );
      });
    }
  };
}