Формирование сообщений об ошибках в class-validator строится вокруг
механизма ValidationError, который возвращается после
выполнения валидации. Каждый такой объект содержит структурированную
информацию о нарушениях, а не просто строку с текстом ошибки. Это
позволяет гибко преобразовывать ошибки в любой формат: от простого
текста до сложных локализованных сообщений и структурированных
API-ответов.
Каждая ошибка валидации представляет собой объект следующего вида:
property — имя свойства, которое не прошло
валидациюvalue — значение, которое было провереноconstraints — набор нарушенных правилchildren — вложенные ошибки (для объектов и
массивов)target — исходный объектКлючевым полем для формирования сообщений является
constraints. Именно оно содержит текстовые сообщения,
связанные с конкретными валидаторами.
Пример структуры:
{
property: "email",
value: "wrong-email",
constraints: {
isEmail: "email must be an email"
}
}
Каждый встроенный декоратор может возвращать сообщение по умолчанию. Например:
import { IsEmail } from "class-validator";
class User {
@IsEmail()
email;
}
При ошибке будет возвращено сообщение из стандартного набора правил. Однако этот текст можно переопределить через параметры декоратора.
Наиболее простой способ кастомизации:
import { IsEmail } from "class-validator";
class User {
@IsEmail({}, { message: "Некорректный формат email" })
email;
}
В этом случае значение constraints.isEmail заменяется на
заданную строку.
Такой подход применяется, когда:
Более гибкий механизм — использование функции в
message.
Функция получает объект контекста:
propertyvalueconstraintstargetПример:
import { MinLength } from "class-validator";
class User {
@MinLength(8, {
message: (args) =>
`Поле ${args.property} слишком короткое. Получено: ${args.value}`
})
password;
}
Здесь сообщение формируется динамически на основе входных данных.
Многие валидаторы передают параметры в constraints, что
позволяет строить информативные ошибки.
Пример с минимальной длиной:
import { MinLength } from "class-validator";
class User {
@MinLength(8, {
message: (args) =>
`${args.property} должен содержать минимум ${args.constraints[0]} символов`
})
password;
}
args.constraints[0] содержит значение
8.
При масштабных проектах применяется централизованная генерация сообщений.
const buildMessage = (field, rule, value) =>
`Ошибка в поле ${field}: нарушение правила ${rule}, значение: ${value}`;
Использование:
import { IsInt, Min } from "class-validator";
class Product {
@IsInt({
message: (args) =>
buildMessage(args.property, "isInt", args.value)
})
price;
}
Такой подход обеспечивает единый стиль ошибок по всему приложению.
При работе с вложенными структурами ValidationError
содержит children. Сообщения формируются рекурсивно.
class Address {
@IsEmail()
email;
}
class User {
@ValidateNested()
address;
}
Ошибка в address.email будет выглядеть как вложенная
структура:
user
address
Для формирования строки ошибки требуется обход дерева:
function flattenErrors(errors) {
return errors.flatMap(error => {
if (error.children && error.children.length) {
return flattenErrors(error.children);
}
return Object.values(error.constraints || {});
});
}
При создании собственного валидатора сообщение задаётся через
ValidationArguments.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments
} from "class-validator";
@ValidatorConstraint({ name: "isEven", async: false })
class IsEvenConstraint {
validate(value) {
return value % 2 === 0;
}
defaultMessage(args) {
return `${args.property} должно быть чётным числом`;
}
}
Использование:
import { Validate } from "class-validator";
class NumberModel {
@Validate(IsEvenConstraint)
value;
}
Метод defaultMessage становится источником текста
ошибки, если message не переопределён.
Система формирования сообщений имеет строгий порядок:
message в декоратореdefaultMessage кастомного валидатораЭто позволяет комбинировать встроенные и пользовательские механизмы.
Для сложных сценариев применяется функция, возвращающая функцию:
const minMessage = (min) => (args) =>
`${args.property} не может быть меньше ${min}`;
Использование:
import { Min } from "class-validator";
class Account {
@Min(10, { message: minMessage(10) })
balance;
}
Такой подход удобен при повторном использовании правил.
Один из частых сценариев — перевод ошибок.
const messages = {
en: {
required: "Field is required"
},
ru: {
required: "Поле обязательно"
}
};
const t = (lang, key) => messages[lang][key];
class Login {
@IsNotEmpty({
message: () => t("ru", "required")
})
username;
}
Подобный механизм позволяет отделить логику валидации от текстов.
Объект ValidationArguments содержит дополнительные
поля:
object — текущий экземпляр классаvalue — проверяемое значениеconstraints — параметры декоратораtargetName — имя классаЭто позволяет строить контекстные сообщения:
message: (args) =>
`${args.targetName}.${args.property} содержит недопустимое значение`
При обработке API часто требуется преобразование массива ошибок в единый формат:
function formatErrors(errors) {
return errors.map(err => ({
field: err.property,
errors: Object.values(err.constraints || {})
}));
}
Результат:
[
{
"field": "email",
"errors": ["email must be an email"]
}
]
Хотя сам валидатор может быть асинхронным, сообщение остаётся синхронным. Однако допускается использование заранее подготовленных данных:
const dictionary = await loadDictionary();
const message = (args) =>
dictionary[args.property] || "Ошибка валидации";
В архитектурах с сервисным слоем часто выполняется постобработка:
function mapValidationErrors(errors) {
return errors.reduce((acc, err) => {
if (err.constraints) {
acc.push({
field: err.property,
message: Object.values(err.constraints)[0]
});
}
return acc;
}, []);
}
При валидации массивов ошибки могут дублироваться на уровне индексов:
class User {
@IsEmail({}, { each: true })
emails;
}
Ошибки будут иметь вложенные children, где каждое
сообщение относится к конкретному элементу массива.
При проектировании сообщений учитываются следующие принципы:
Пример улучшенного сообщения:
message: (args) =>
`Поле ${args.property} должно содержать корректный email-адрес`
При использовании transform (например, в связке с DTO)
сообщения могут зависеть от приведённого типа данных. Это влияет на
значение value, доступное в
ValidationArguments, что позволяет формировать более точные
ошибки:
message: (args) =>
`Значение "${args.value}" не соответствует требованиям поля ${args.property}`