Документирование правил валидации

Валидация данных в приложениях на JavaScript с использованием class-validator строится на декларативных правилах, описанных через декораторы. Однако сами по себе декораторы не решают задачу понимания этих правил в контексте команды, поддержки кода и предсказуемого поведения API. Документирование превращает набор технических ограничений в формализованную спецификацию поведения данных.

Документирование правил валидации охватывает несколько уровней:

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

Описание правил через декораторы как форма документации

Каждый декоратор в class-validator фактически уже является частью документации. Например:

import { IsString, Length, IsOptional } from "class-validator";

export class CreateUserDto {
  @IsString()
  @Length(3, 20)
  username: string;

  @IsOptional()
  @IsString()
  bio?: string;
}

В этом фрагменте:

  • @IsString() фиксирует тип данных;
  • @Length(3, 20) задаёт диапазон допустимой длины;
  • @IsOptional() определяет необязательность поля.

Однако без дополнительного контекста такие правила остаются полуформальными: они описывают «как проверять», но не «почему именно так».


Сообщения ошибок как часть спецификации

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

В class-validator сообщения задаются через ValidationOptions:

import { IsEmail } from "class-validator";

export class UserDto {
  @IsEmail({}, {
    message: "Email должен соответствовать формату user@example.com"
  })
  email: string;
}

Сообщение становится частью контракта:

  • оно описывает ожидаемый формат;
  • фиксирует пользовательское поведение;
  • может использоваться в UI или API-ответах без дополнительной интерпретации.

Динамические сообщения

Сообщения могут зависеть от параметров:

import { Length } from "class-validator";

export class ProductDto {
  @Length(5, 100, {
    message: ({ constraints }) =>
      `Название должно быть от ${constraints[0]} до ${constraints[1]} символов`
  })
  title: string;
}

Здесь constraints становятся частью документированного контракта валидатора.


Контекст и расширенные сообщения

В class-validator доступен доступ к ValidationArguments, который позволяет формировать контекстно-зависимые описания:

import { registerDecorator, ValidationArguments } from "class-validator";

function IsEven() {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: "isEven",
      target: object.constructor,
      propertyName,
      validator: {
        validate(value: any) {
          return typeof value === "number" && value % 2 === 0;
        },
        defaultMessage(args: ValidationArguments) {
          return `Значение ${args.property} должно быть чётным числом`;
        }
      }
    });
  };
}

Такой подход позволяет:

  • документировать смысл проверки через defaultMessage;
  • связывать ошибку с именем поля (args.property);
  • формировать единообразные описания поведения.

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

Группы (groups) позволяют разделять правила в зависимости от сценария использования DTO. Это важный инструмент документирования контекстов.

import { IsString, Length } from "class-validator";

export class UpdateUserDto {
  @IsString({ groups: ["update"] })
  @Length(3, 20, { groups: ["create", "update"] })
  username: string;
}

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

  • при создании пользователя действует полный набор ограничений;
  • при обновлении часть правил может изменяться.

Группы фактически описывают жизненный цикл данных.


Условная валидация и явное описание зависимостей

Условия через ValidateIf фиксируют зависимости между полями:

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

export class ProfileDto {
  @ValidateIf(o => o.isPublic === true)
  @IsString()
  displayName: string;

  isPublic: boolean;
}

Документирование здесь выражается через:

  • явное указание зависимости displayName → isPublic;
  • декларацию логики применения правила.

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


Кастомные валидаторы как самостоятельные документированные единицы

Кастомные валидаторы в class-validator часто становятся повторно используемыми бизнес-правилами. Их документирование должно быть автономным.

import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";

@ValidatorConstraint({ name: "isPhoneNumber", async: false })
export class IsPhoneNumberConstraint implements ValidatorConstraintInterface {
  validate(value: string) {
    return /^\+?[0-9]{10,15}$/.test(value);
  }

  defaultMessage() {
    return "Номер телефона должен содержать от 10 до 15 цифр";
  }
}

Такая реализация фиксирует:

  • формат правила через регулярное выражение;
  • текстовое описание поведения;
  • повторяемость логики в разных DTO.

Централизация правил и переиспользование

Документирование усиливается при выносе правил в отдельные сущности:

import { IsString, Length } from "class-validator";

export const UsernameRules = [
  IsString(),
  Length(3, 20)
];

Использование:

export class UserDto {
  @UsernameRules
  username: string;
}

(в реальной практике чаще применяются фабрики декораторов или функции-обёртки)

Централизация позволяет:

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

Документирование через типизацию и DTO-структуру

Сама структура DTO уже является формой документации:

export class OrderDto {
  productId: number;
  quantity: number;
  comment?: string;
}

Добавление валидаторов превращает DTO в формальную спецификацию:

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

Таким образом, DTO становится одновременно моделью данных и документацией API.


Согласованность сообщений и стандартизация формата

Одним из критических аспектов документирования является единообразие сообщений.

Рекомендуемая структура сообщений:

  • одинаковый стиль формулировок;
  • отсутствие дублирующей информации;
  • привязка к полю через args.property;
  • единый формат длины и структуры.

Пример стандартизации:

message: ({ property }) =>
  `Поле ${property} содержит недопустимое значение`

Такой подход обеспечивает:

  • предсказуемость ошибок;
  • возможность автоматической обработки;
  • упрощение локализации.

Использование контекста валидации

class-validator позволяет передавать context, который может использоваться для документирования бизнес-сценариев:

validate(value, args) {
  const context = args?.constraints?.[0];
  return context === "strict" ? value > 10 : value >= 10;
}

Контекст в данном случае становится частью описания поведения:

  • один и тот же валидатор ведёт себя по-разному;
  • логика явно зафиксирована через параметры;
  • поведение документируется через контракт использования.

Документирование сложных зависимостей между правилами

При росте сложности DTO важно фиксировать взаимодействие правил:

@ValidateIf(o => o.type === "premium")
@Length(10, 200)
description: string;

Такие конструкции документируют:

  • зависимость поля от типа сущности;
  • изменение требований в зависимости от состояния;
  • контекстное применение ограничений.

Формирование читаемой спецификации из валидаторов

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

  • типы определяют структуру;
  • декораторы определяют ограничения;
  • сообщения описывают поведение при ошибках;
  • группы определяют сценарии;
  • кастомные валидаторы фиксируют бизнес-логику.

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