NestJS / 37 минут

Преобразование параметров и pipes

Преобразуйте и проверяйте параметры до вызова сценария.

В процессе Теория: впереди Практика: впереди Тест: впереди
06 / 14
После этой главы
  • Преобразуйте и проверяйте параметры до вызова сценария.
  • Применить тему в задаче «Преобразование параметров и pipes» и объяснить граничные случаи.

Около 7 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Между HTTP и аргументом метода

Pipe обрабатывает значение перед передачей в метод контроллера. Он может проверить структуру, преобразовать тип или отклонить запрос исключением. Это позволяет обработчику работать с понятным аргументом вместо повторяющегося разбора строк. Guard решает вопрос допуска, а pipe — вопрос пригодности входа; одна стадия не заменяет другую.

Маршрут /items/42 содержит текстовый параметр. Аннотация id: number существует только для компилятора и сама по себе оставляет строку. Встроенный ParseIntPipe выполняет реальную проверку и преобразование, поэтому арифметика в обработчике больше не зависит от неявного приведения JavaScript.

TypeScript
@Controller('items')
class ItemsController {
  @Get(':id')
  find(@Param('id', ParseIntPipe) id: number) {
    return { id, next: id + 1 };
  }
}

Синтаксис числа и смысл числа

ParseIntPipe принимает целочисленную запись и отклоняет обычный нечисловой текст или дробь. Однако отрицательное целое остаётся целым. Если идентификаторы должны быть положительными, добавьте отдельное правило. Не смешивайте задачу преобразования и бизнес-требование: иначе возникает ложная уверенность, что framework уже проверил все ограничения.

Число может оказаться больше безопасного диапазона JavaScript. Для больших идентификаторов допустим контракт строкового значения или явная проверка Number.isSafeInteger. Вход ограничивайте до обращения в БД. Даже правильная по синтаксису строка не доказывает существование записи и право пользователя её читать.

Значение по умолчанию

Query-параметр может отсутствовать. DefaultValuePipe задаёт значение до следующего преобразования; порядок pipes существенен. Пропущенный параметр и переданное некорректное значение обычно имеют разный смысл: default должен помогать первому случаю, а не незаметно исправлять произвольный ошибочный ввод.

TypeScript
@Get()
list(
  @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
) {
  return { page };
}

У этой заготовки ещё нет ограничения положительности и максимума. Для настоящей пагинации добавьте границы и лимит размера страницы. Проверяйте отсутствие page, ноль, отрицательное число, повторяющийся параметр и большой текст. Так вы документируете поведение API точнее, чем одной счастливой проверкой page=2.

Собственный PipeTransform

Пользовательский pipe реализует transform. Метод получает значение и при необходимости ArgumentMetadata, возвращает подготовленное значение или бросает исключение. Не возвращайте BadRequestException как обычный результат: тогда обработчик получит объект ошибки вместо отказа. Для асинхронной проверки можно вернуть Promise, но запрос к хранилищу лучше оставлять сценарию, если он проверяет бизнес-состояние.

TypeScript
@Injectable()
class PositivePipe implements PipeTransform<number, number> {
  transform(value: number) {
    if (!Number.isSafeInteger(value) || value < 1) {
      throw new BadRequestException('Ожидается положительное целое');
    }
    return value;
  }
}

Примените PositivePipe после ParseIntPipe. Первый шаг получает число, а не строку. Проверка typeof без понимания предыдущего шага дала бы иной контракт. Вынесенный pipe полезен, когда правило повторяется и имеет устойчивое имя; ради одноразовой проверки не нужно создавать библиотеку из десятков абстракций.

Область и зависимости

Pipe можно назначить параметру, обработчику, контроллеру или глобально. Выбирайте наименьшую область, в которой правило действительно общее. Валидация конкретного id на всём приложении способна неожиданно затронуть DTO тела и другие маршруты. Глобальный ValidationPipe обычно проверяет DTO по их метаданным, а специализированный pipe привязывают явно.

Если pipe получает зависимости через Nest DI, регистрируйте его так, чтобы контейнер создавал экземпляр. Ручной new удобен для простой конфигурации, но не включает автоматическое внедрение конструктора. Глобальный APP_PIPE — вариант регистрации через модуль, когда pipe нуждается в провайдерах. Следите, чтобы общий pipe не сохранял значения последнего запроса в singleton-поле.

Практика HTTP-параметра

Создайте GET /items/:id с настоящим ParseIntPipe. solve отправляет id как часть пути и возвращает статус: 200 для целой записи и 400 для нечислового или дробного значения. В этой задаче отрицательное целое допустимо, потому что условие проверяет именно преобразование, а не доменную положительность.

Используйте encodeURIComponent при построении URL и закройте приложение в finally. Не подменяйте pipe ручной проверкой статуса в solve: HTTP-запрос должен пройти зарегистрированный параметр контроллера. Затем усложните локальный проект, добавив PositivePipe, и объясните, почему поведение для отрицательного числа изменилось только после нового бизнес-ограничения.

Загружаем редактор…
Следующая темаGuards и авторизация

Музыка: Reverie — Scott Buckley · CC BY 4.0