Доступ к значению и ограничениям в сообщениях

Библиотека class-validator предоставляет механизм генерации сообщений об ошибках, который тесно связан с контекстом выполнения валидатора. Сообщение может формироваться как статическая строка, так и как функция, получающая полный набор данных о проверяемом значении, ограничениях и объекте, к которому относится свойство.

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

Основной интерфейс, через который передаются данные в сообщение:

export interface ValidationArguments {
  value: any;
  constraints: any[];
  targetName: string;
  object: object;
  property: string;
  // дополнительные поля в зависимости от контекста
}

Эти данные формируют основу для построения гибких правил отображения ошибок.


Доступ к значению валидируемого свойства

Наиболее часто используемая часть контекста — value. Оно содержит текущее значение свойства, проходящего проверку.

Пример использования value в сообщении

import { MinLength } from "class-validator";

export class User {
  @MinLength(5, {
    message: (args) => `Значение "${args.value}" слишком короткое`,
  })
  username: string;
}

Здесь сообщение формируется динамически, и ошибка всегда содержит фактическое значение, вызвавшее нарушение ограничения.

Использование value особенно важно в случаях:

  • отладки сложных валидаций
  • логирования некорректных данных
  • формирования человекочитаемых сообщений
  • построения UI-ошибок с контекстом ввода

Доступ к ограничениям (constraints)

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

Структура constraints

Каждый декоратор передаёт собственный набор ограничений:

  • MinLength(min)
  • MaxLength(max)
  • Length(min, max)
  • Min(value)
  • Max(value)

Эти параметры доступны в том порядке, в котором были объявлены.

Пример использования constraints

import { MinLength } from "class-validator";

export class Product {
  @MinLength(3, {
    message: (args) =>
      `Минимальная длина: ${args.constraints[0]}, текущее значение: ${args.value.length}`,
  })
  title: string;
}

В данном случае:

  • args.constraints[0] → минимальная длина
  • args.value → фактическое значение

Сочетание value и constraints

На практике value и constraints используются совместно для построения контекстных сообщений.

import { Between } from "class-validator";

export class Order {
  @Between(10, 100, {
    message: (args) => {
      const [min, max] = args.constraints;
      return `Значение ${args.value} выходит за пределы [${min}, ${max}]`;
    },
  })
  amount: number;
}

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


Полный контекст ValidationArguments

Помимо value и constraints, объект содержит дополнительные поля, которые позволяют строить сообщения с привязкой к структуре данных.

object

object — это экземпляр класса, в котором выполняется валидация.

message: (args) => {
  return `Ошибка в объекте: ${JSON.stringify(args.object)}`;
}

Используется для:

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

property

Имя свойства, на котором произошла ошибка.

message: (args) => `Ошибка в поле "${args.property}"`;

Позволяет унифицировать обработку ошибок без привязки к конкретным классам.


targetName

Имя класса, в котором происходит валидация.

message: (args) =>
  `Ошибка в сущности ${args.targetName}, поле ${args.property}`;

Полезно при логировании и трассировке ошибок в сложных доменных моделях.


Использование в кастомных валидаторах

При создании собственного валидатора через ValidatorConstraintInterface доступ к аргументам сохраняется через ValidationArguments.

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

@ValidatorConstraint({ name: "customText", async: false })
export class CustomTextValidator implements ValidatorConstraintInterface {
  validate(text: string, args: ValidationArguments) {
    return typeof text === "string" && text.length > 2;
  }

  defaultMessage(args: ValidationArguments) {
    return `Значение "${args.value}" не соответствует правилам поля "${args.property}"`;
  }
}

Здесь:

  • validate отвечает за логику проверки
  • defaultMessage формирует сообщение с доступом ко всему контексту

Динамические сообщения в зависимости от объекта

object позволяет учитывать состояние других полей.

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

export class Account {
  @ValidateIf((o) => o.isActive)
  @IsNotEmpty({
    message: (args) =>
      `Поле "${args.property}" обязательно, так как аккаунт активен`,
  })
  email: string;

  isActive: boolean;
}

Хотя здесь используется только args.property, доступ к args.object позволяет расширять логику до межполей.


Типизация и безопасность доступа

В TypeScript часто возникает необходимость строго типизировать object:

message: (args: ValidationArguments) => {
  const obj = args.object as User;
  return `Пользователь ${obj.id}, ошибка в ${args.property}`;
};

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


Ограничения и особенности поведения

Некоторые особенности работы контекста:

  • value может быть undefined при отсутствии значения
  • constraints зависит от конкретного декоратора и не имеет единого формата
  • object содержит исходный экземпляр, а не сериализованную версию
  • сообщения вычисляются при каждой ошибке отдельно

Работа с вложенными объектами

При валидации вложенных структур property отражает локальный путь свойства.

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

class Address {
  city: string;
}

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

В случае ошибки:

  • property может быть "address.city"
  • value будет значением city
  • object будет ссылаться на Address

Использование constraints в сложных валидаторах

Некоторые валидаторы используют несколько параметров:

import { Length } from "class-validator";

export class Comment {
  @Length(10, 200, {
    message: (args) => {
      const [min, max] = args.constraints;
      const len = (args.value || "").length;

      return `Длина ${len}, допустимый диапазон: ${min}-${max}`;
    },
  })
  text: string;
}

Такой подход позволяет строить сообщения, не дублируя бизнес-логику.


Централизация логики сообщений

В крупных проектах часто выделяют функцию построения сообщений:

function lengthMessage(args: ValidationArguments) {
  const [min, max] = args.constraints;
  return `Поле ${args.property}: ожидалась длина ${min}-${max}, получено ${args.value.length}`;
}

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

@Length(5, 15, { message: lengthMessage })
username: string;

Это снижает дублирование и упрощает поддержку правил отображения ошибок.


Контекст как основа расширяемой валидации

Модель ValidationArguments фактически превращает сообщение об ошибке в полноценный слой логики, способный:

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

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