Есть ли способ собрать все методы и их пути из приложения NestJS? - PullRequest
0 голосов
/ 18 февраля 2019

Мне нужно написать промежуточное программное обеспечение для обработки запросов, но некоторые пути должны быть исключены.Я не хочу вручную жестко кодировать их все, поэтому у меня есть идея:

Создать специальный декоратор, который будет помечать методы для исключения, что-то вроде этого:

import { ReflectMetadata } from '@nestjs/common';
export const Exclude = () =>
  ReflectMetadata('exclude', 'true');

Есть лиСпособ после создания приложения NestJS каким-то образом рекурсивно заставить все методы, аннотированные этим декоратором, автоматически добавлять их пути для исключения в моем промежуточном программном обеспечении?

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Я опубликовал модуль многократного использования для обнаружения метаданных в ваших обработчиках или классах Injectable специально для поддержки этого шаблона.Вы можете получить @nestjs-plus/common из NPM, а затем использовать DiscoveryService для автоматического получения всех соответствующих обработчиков или классов на основе предоставленного вами токена MetaData.Исходный код доступен на Github .Я буду продолжать обновлять документы в ближайшее время, но есть несколько примеров его использования, уже включенных в репозиторий.

Под капотом он использует MetaDataScanner, но оборачивает вещи в приятный и простой в использованииAPI.Посмотрите на фрагмент, который вы разместили, это может помочь сократить значительное количество шаблонов для вашего конкретного случая использования.В модуле @nestjs-plus/rabbitmq (из того же репозитория) вы можете увидеть более продвинутые способы использования этого для склейки расширенной функциональности .

РЕДАКТИРОВАТЬ: я обновил библиотекуподдерживать сценарии обнаружения контроллеров и методы контроллеров для поддержки вашего сценария. Существует полный набор тестов, который имитирует ваши настройки с помощью декоратора @Roles, который вы можете проверить. .После включения DiscoveryModule в ваш импорт и введения DiscoverService вы можете найти все методы контроллера, используя упрощенный methodsAndControllerMethodsWithMeta API.

// Inject the service
constructor(private readonly discover: DiscoveryService) { }

// Discover all controller methods decorated with guest roles or 
// belonging to controllers with guest roles

const allMethods = this.discover.methodsAndControllerMethodsWithMeta<string[]>(
  rolesMetaKey,
  x => x.includes('guest')
);

После того, как вы обнаружите все методы,после того, как вы сможете делать с ними все, что захотите, в вашем случае создайте коллекцию их RequestMethod и path.

const fullPaths = allGuestMethods.map(x => {
  const controllerPath = Reflect.getMetadata(
    PATH_METADATA,
    x.component.metatype
  );

  const methodPath = Reflect.getMetadata(PATH_METADATA, x.handler);
  const methodHttpVerb = Reflect.getMetadata(
    METHOD_METADATA,
    x.handler
  );

  return {
    verb: methodHttpVerb,
    path: `${controllerPath}/${methodPath}`
  }
});

, которая выдаст вам нечто подобное (взято из связанного тестаsuite).

expect(fullPaths).toContainEqual({verb: RequestMethod.GET, path: 'guest/route-path-one'});
expect(fullPaths).toContainEqual({verb: RequestMethod.GET, path: 'super/route-path-two'});
expect(fullPaths).toContainEqual({verb: RequestMethod.POST, path: 'admin/route-path-three'});

Не стесняйтесь оставлять отзывы о подходе / API.

0 голосов
/ 19 февраля 2019

Итак ... помогите себе.

После поиска источников NestJS я нашел способ, вот направление для тех, кто заинтересовался:

import * as pathToRegexp from 'path-to-regexp';
import { INestApplication, RequestMethod } from '@nestjs/common';
import { NestContainer } from '@nestjs/core/injector/container';
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
import { PATH_METADATA, MODULE_PATH, METHOD_METADATA } from '@nestjs/common/constants';

const trimSlashes = (str: string) => {
  if (str != null && str.length) {
    while (str.length && str[str.length - 1] === '/') {
      str = str.slice(0, str.length - 1);
    }
  }
  return str || '';
};

const joinPath = (...p: string[]) =>
  '/' + trimSlashes(p.map(trimSlashes).filter(x => x).join('/'));

// ---------------8<----------------

const app = await NestFactory.create(AppModule);

// ---------------8<----------------

const excludes = Object.create(null);
const container: NestContainer = (app as any).container; // this is "protected" field, so a bit hacky here
const modules = container.getModules();
const scanner = new MetadataScanner();

modules.forEach(({ routes, metatype }, moduleName) => {
  let modulePath = metatype ? Reflect.getMetadata(MODULE_PATH, metatype) : undefined;
  modulePath = modulePath ? modulePath + globalPrefix : globalPrefix;

  routes.forEach(({ instance, metatype }, controllerName) => {
    const controllerPath = Reflect.getMetadata(PATH_METADATA, metatype);
    const isExcludeController = Reflect.getMetadata('exclude', metatype) === 'true';
    const instancePrototype = Object.getPrototypeOf(instance);

    scanner.scanFromPrototype(instance, instancePrototype, method => {
      const targetCallback = instancePrototype[method];
      const isExcludeMethod = Reflect.getMetadata('exclude', targetCallback) === 'true';

      if (isExcludeController || isExcludeMethod) {
        const requestMethod: RequestMethod = Reflect.getMetadata(METHOD_METADATA, targetCallback);
        const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);

        // add request method to map, if doesn't exist already
        if (!excludes[RequestMethod[requestMethod]]) {
          excludes[RequestMethod[requestMethod]] = [];
        }

        // add path to excludes
        excludes[RequestMethod[requestMethod]].push(
          // transform path to regexp to match it later in middleware
          pathToRegexp(joinPath(modulePath, controllerPath, routePath)),
        );
      }
    });
  });
});

// now you can use `excludes` map in middleware
...