Библиотека class-validator предоставляет механизм генерации сообщений об ошибках, который тесно связан с контекстом выполнения валидатора. Сообщение может формироваться как статическая строка, так и как функция, получающая полный набор данных о проверяемом значении, ограничениях и объекте, к которому относится свойство.
Ключевая идея заключается в том, что сообщение — это не просто текст, а результат работы функции, имеющей доступ к внутреннему состоянию валидации.
Основной интерфейс, через который передаются данные в сообщение:
export interface ValidationArguments {
value: any;
constraints: any[];
targetName: string;
object: object;
property: string;
// дополнительные поля в зависимости от контекста
}
Эти данные формируют основу для построения гибких правил отображения ошибок.
Наиболее часто используемая часть контекста — value. Оно
содержит текущее значение свойства, проходящего проверку.
import { MinLength } from "class-validator";
export class User {
@MinLength(5, {
message: (args) => `Значение "${args.value}" слишком короткое`,
})
username: string;
}
Здесь сообщение формируется динамически, и ошибка всегда содержит фактическое значение, вызвавшее нарушение ограничения.
Использование value особенно важно в случаях:
constraints представляет собой массив параметров,
переданных в декоратор валидатора. Эти значения задают правила проверки
и позволяют использовать их при формировании сообщений.
Каждый декоратор передаёт собственный набор ограничений:
MinLength(min)MaxLength(max)Length(min, max)Min(value)Max(value)Эти параметры доступны в том порядке, в котором были объявлены.
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
используются совместно для построения контекстных сообщений.
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;
}
Такой подход позволяет избежать жёстко закодированных текстов и делает сообщения адаптивными к изменению правил валидации.
Помимо value и constraints, объект содержит
дополнительные поля, которые позволяют строить сообщения с привязкой к
структуре данных.
object — это экземпляр класса, в котором выполняется
валидация.
message: (args) => {
return `Ошибка в объекте: ${JSON.stringify(args.object)}`;
}
Используется для:
Имя свойства, на котором произошла ошибка.
message: (args) => `Ошибка в поле "${args.property}"`;
Позволяет унифицировать обработку ошибок без привязки к конкретным классам.
Имя класса, в котором происходит валидация.
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 будет значением cityobject будет ссылаться на AddressНекоторые валидаторы используют несколько параметров:
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 фактически превращает
сообщение об ошибке в полноценный слой логики, способный:
Такой подход позволяет отделить проверку данных от представления ошибок, сохраняя гибкость при изменении бизнес-правил.