Композиция валидаторов

Базовый принцип наложения декораторов

В Class-validator каждый класс рассматривается как набор правил, применяемых к его свойствам через декораторы. Композиция валидаторов возникает уже на уровне простого перечисления нескольких декораторов над одним полем.

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

import { IsString, Length, IsNotEmpty } from 'class-validator';

class UserDto {
  @IsString()
  @IsNotEmpty()
  @Length(3, 20)
  username;
}

В данном примере свойство username одновременно проходит несколько независимых проверок:

  • проверка типа строки
  • проверка на пустое значение
  • проверка длины

Композиция здесь реализуется не через объединение функций, а через стек метаданных, который формируется библиотекой.


Порядок выполнения и накопление ошибок

Важной особенностью является то, что порядок декораторов не всегда влияет на порядок выполнения правил. Class-validator собирает метаданные, а затем запускает пайплайн валидации.

@IsNotEmpty()
@Length(5, 10)
@IsString()
field;

Несмотря на визуальный порядок, выполнение происходит как единый набор правил. Результатом становится массив ошибок, каждая из которых содержит:

  • constraint name
  • сообщение
  • контекст свойства
  • значение

Композиция валидаторов таким образом ориентирована не на последовательное прерывание, а на полное накопление ошибок.


Композиция через независимые декораторы

Каждый декоратор можно рассматривать как функцию-валидатор, зарегистрированную в системе метаданных. При объединении нескольких декораторов формируется логическое пересечение условий (AND-логика).

@IsString()
@IsEmail()
email;

Значение считается валидным только при выполнении всех условий одновременно.


Группы валидации как механизм композиции контекстов

Одним из мощных механизмов композиции выступают группы. Они позволяют разделять наборы правил по сценариям использования одной и той же модели.

import { IsEmail, IsOptional } from 'class-validator';

class UserDto {
  @IsEmail({}, { groups: ['create'] })
  email;

  @IsEmail({}, { groups: ['update'] })
  @IsOptional()
  email;
}

Группы позволяют формировать различные комбинации валидаторов без изменения структуры класса. Валидация становится контекстно-зависимой.

Механика групп реализует композицию на уровне сценариев:

  • базовая модель
  • сценарий создания
  • сценарий обновления
  • частичные обновления

Условная композиция через validateIf

Условные валидаторы позволяют включать или исключать правила в зависимости от состояния объекта.

import { ValidateIf, IsString } from 'class-validator';

class UserDto {
  @ValidateIf(o => o.isActive === true)
  @IsString()
  nickname;

  isActive;
}

Здесь формируется динамическая композиция:

  • если условие истинно, применяется цепочка валидаторов
  • если ложно, поле исключается из проверки

Таким образом создаётся ленивое объединение правил, зависящее от данных объекта.


Вложенные объекты и рекурсивная композиция

Сложные структуры данных требуют вложенной валидации. Class-validator поддерживает рекурсивную композицию через ValidateNested.

import { ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';

class Address {
  @IsString()
  city;
}

class UserDto {
  @ValidateNested()
  @Type(() => Address)
  address;
}

Здесь композиция становится иерархической:

  • верхний уровень валидаторов
  • вложенный объект
  • каскадная проверка свойств вложенного класса

Такая структура формирует дерево валидаторов, где каждый узел содержит собственный набор правил.


Пользовательские валидаторы как элемент композиции

Расширение системы выполняется через создание кастомных декораторов с registerDecorator.

import { registerDecorator } from 'class-validator';

function IsEven() {
  return function (object, propertyName) {
    registerDecorator({
      name: 'isEven',
      target: object.constructor,
      propertyName,
      validator: {
        validate(value) {
          return typeof value === 'number' && value % 2 === 0;
        }
      }
    });
  };
}

Кастомные валидаторы интегрируются в общую систему композиции наравне со встроенными.

При использовании нескольких кастомных и встроенных правил формируется единый пайплайн проверки.


Комбинирование логики через составные условия

Более сложные композиции строятся через объединение логических условий внутри одного валидатора.

import { ValidateIf, Min, Max } from 'class-validator';

class Product {
  @ValidateIf(o => o.price !== null)
  @Min(10)
  @Max(1000)
  price;
}

Здесь наблюдается композиция двух уровней:

  • уровень включения (ValidateIf)
  • уровень диапазона значений (Min, Max)

Такая схема позволяет строить многоуровневые правила без усложнения структуры класса.


Композиция через трансформацию данных

Хотя Class-validator не занимается трансформацией напрямую, в связке с class-transformer возникает комбинированная модель обработки:

import { Type } from 'class-transformer';
import { IsInt, Min } from 'class-validator';

class Order {
  @Type(() => Number)
  @IsInt()
  @Min(1)
  quantity;
}

Композиция здесь включает два этапа:

  • преобразование входных данных
  • последующая валидация уже приведённого значения

Таким образом формируется конвейер обработки данных.


Составные правила через логическое разделение ответственности

Композиция валидаторов становится наиболее эффективной при разделении ответственности между декораторами:

  • один декоратор отвечает за тип
  • другой за диапазон
  • третий за контекст
  • четвёртый за структуру
@IsString()
@Length(5, 50)
@IsOptional()
title;

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


Переиспользование наборов валидаторов

Часто возникает необходимость повторного использования одинаковых наборов правил. Это достигается через создание составных декораторов.

import { applyDecorators } from '@nestjs/common';
import { IsString, Length } from 'class-validator';

function IsShortText() {
  return applyDecorators(
    IsString(),
    Length(3, 30)
  );
}

Такой подход формирует абстракцию над набором правил, превращая их в единый логический блок.

Композиция в этом случае переносится на уровень архитектуры приложения.


Конфликт и приоритет валидаторов

При наложении множества правил возможны ситуации пересечения логики. Class-validator не разрешает конфликты автоматически, а просто агрегирует ошибки.

@Min(10)
@Max(5)
value;

В этом случае оба условия могут быть проверены независимо, и результатом станет набор противоречивых ошибок.

Композиция здесь не включает механизм разрешения конфликтов, что перекладывает ответственность на проектирование схемы валидаторов.


Композиция как дерево метаданных

Внутренняя модель Class-validator основана на метаданных, где каждый класс преобразуется в структуру:

  • класс

    • свойство

      • список валидаторов

        • условия
        • сообщения
        • ограничения

Такая структура позволяет:

  • объединять правила
  • расширять их без изменения исходного класса
  • применять различные стратегии валидации

Многоуровневые схемы валидации

При работе со сложными DTO композиция валидаторов формирует многоуровневые схемы:

  • примитивные поля
  • вложенные объекты
  • массивы с ValidateNested
  • условные ветвления
  • групповые сценарии
class CartItem {
  @IsString()
  id;

  @Min(1)
  quantity;
}

class Cart {
  @ValidateNested({ each: true })
  @Type(() => CartItem)
  items;
}

Здесь формируется каскадная композиция на уровне коллекций объектов.


Итоговая модель композиции

Композиция валидаторов в Class-validator представляет собой сочетание нескольких механизмов:

  • стек декораторов на уровне свойства
  • контекстные группы
  • условные проверки
  • рекурсивные структуры
  • пользовательские расширения
  • трансформационные цепочки
  • повторно используемые наборы правил

Все эти элементы формируют единый декларативный подход к описанию правил валидации, где сложность распределяется между независимыми, слабо связанными компонентами.