В библиотеке class-validator сообщения об ошибках не обязаны быть статичными строками. Во многих практических сценариях требуется формировать текст ошибки на основе состояния объекта, параметров декоратора или значения самого поля. Для этого используются функции, возвращающие строку сообщения.
Базовый принцип заключается в том, что вместо строки в свойство
message передаётся функция:
message: (validationArguments) => string
Эта функция получает объект ValidationArguments,
содержащий контекст текущей валидации, и возвращает итоговое
сообщение.
Объект ValidationArguments является ключевым элементом
при построении динамических сообщений. Он содержит:
value — текущее значение поляconstraints — массив параметров, переданных в
декораторtargetName — имя классаobject — экземпляр объекта, проходящего валидациюproperty — имя проверяемого свойстваПример структуры:
interface ValidationArguments {
value: any;
constraints: any[];
targetName: string;
object: any;
property: string;
}
Эти данные позволяют формировать сообщения, учитывающие не только значение поля, но и бизнес-логику объекта.
Наиболее частый сценарий — включение фактического значения поля в текст ошибки.
import { IsNotEmpty } from 'class-validator';
class User {
@IsNotEmpty({
message: (args) => `Поле "${args.property}" не должно быть пустым. Получено: ${args.value}`,
})
name: string;
}
Здесь сообщение формируется динамически:
args.propertyargs.valueЭто упрощает отладку и делает ошибки более информативными.
Многие валидаторы принимают дополнительные параметры. Эти параметры
становятся доступны в args.constraints.
Пример с ограничением длины:
import { Length } from 'class-validator';
class Product {
@Length(5, 20, {
message: (args) => {
const [min, max] = args.constraints;
return `Название должно содержать от ${min} до ${max} символов. Текущее значение: "${args.value}"`;
},
})
title: string;
}
Механизм constraints позволяет делать декораторы
универсальными и переиспользуемыми, не хардкодя значения в тексте
ошибок.
Валидация часто зависит не только от одного поля, но и от состояния всего объекта.
import { Min } from 'class-validator';
class Order {
status: string;
@Min(100, {
message: (args) => {
const order = args.object as Order;
if (order.status === 'VIP') {
return `Для VIP-заказов минимальная сумма выше. Текущее значение: ${args.value}`;
}
return `Минимальная сумма заказа — ${args.constraints[0]}`;
},
})
amount: number;
}
Здесь используется:
args.object для доступа ко всему объектуТакой подход позволяет адаптировать сообщения под бизнес-правила без написания отдельных валидаторов.
Для крупных проектов встроенные функции в декораторах быстро становятся громоздкими. Логичнее выносить генерацию сообщений в отдельные функции.
function lengthMessage(args: ValidationArguments): string {
const [min, max] = args.constraints;
return `${args.property}: допустимая длина ${min}-${max}, получено ${args.value}`;
}
class Profile {
@Length(3, 15, {
message: lengthMessage,
})
username: string;
}
Такой подход дает:
Когда требуется стандартизировать сообщения по всему проекту, используется фабрика генераторов.
function createLengthMessage(prefix: string) {
return (args: ValidationArguments) => {
const [min, max] = args.constraints;
return `${prefix}: поле "${args.property}" должно быть от ${min} до ${max} символов`;
};
}
class Account {
@Length(6, 30, {
message: createLengthMessage('Ошибка валидации аккаунта'),
})
login: string;
}
Фабрика позволяет:
Одним из частых применений функций сообщений является интернационализация.
const messages = {
ru: {
required: (field: string) => `Поле ${field} обязательно`,
length: (min: number, max: number) =>
`Длина должна быть от ${min} до ${max} символов`,
},
en: {
required: (field: string) => `Field ${field} is required`,
length: (min: number, max: number) =>
`Length must be between ${min} and ${max} characters`,
},
};
const lang = 'ru';
class Customer {
@IsNotEmpty({
message: (args) => messages[lang].required(args.property),
})
firstName: string;
}
Такой способ позволяет централизованно управлять языковыми пакетами без изменения моделей данных.
Динамические сообщения могут учитывать несколько полей и внешние правила.
class Booking {
type: string;
@Min(1, {
message: (args) => {
const booking = args.object as Booking;
if (booking.type === 'premium') {
return 'Для премиум-бронирования минимальное количество — 5';
}
return 'Минимальное значение должно быть не меньше 1';
},
})
seats: number;
}
Здесь функция сообщения фактически отражает часть бизнес-логики, но без необходимости создавать кастомный валидатор.
Использование функций для сообщений увеличивает гибкость, но создаёт определённые нюансы:
Поэтому динамические сообщения целесообразно применять для:
Для сложной валидации лучше использовать кастомные валидаторы через
ValidatorConstraint.
В кастомных валидаторах функция сообщений используется аналогично:
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
@ValidatorConstraint({ name: 'isPositive', async: false })
class IsPositiveConstraint implements ValidatorConstraintInterface {
validate(value: number) {
return value > 0;
}
defaultMessage(args: ValidationArguments) {
return `Значение ${args.property} должно быть положительным. Получено: ${args.value}`;
}
}
Метод defaultMessage по сути является специализированной
формой функции message, но применяется внутри
constraint-класса.
При использовании TypeScript важно явно приводить типы, особенно при
работе с constraints:
message: (args: ValidationArguments) => {
const [min, max] = args.constraints as [number, number];
return `Допустимый диапазон: ${min}-${max}`;
};
Без явного приведения типы теряются, так как constraints
имеет тип any[].
Иногда сообщение формируется из нескольких уровней данных:
class Discount {
type: string;
@Min(10, {
message: (args) => {
const discount = args.object as Discount;
const base = args.constraints[0];
return `Тип ${discount.type}: минимальное значение скидки ${base}%`;
},
})
percent: number;
}
Такой подход позволяет объединять:
При росте проекта полезно стандартизировать формат сообщений:
function formatError({
field,
rule,
expected,
received,
}: {
field: string;
rule: string;
expected: string | number;
received: any;
}) {
return `[${rule}] Поле "${field}" должно быть ${expected}, получено ${received}`;
}
Использование:
message: (args) =>
formatError({
field: args.property,
rule: 'MIN_LENGTH',
expected: args.constraints[0],
received: args.value,
});
Такой слой абстракции позволяет унифицировать вывод ошибок независимо от валидатора.
Функции сообщений всегда остаются синхронными, даже если сама валидация асинхронная. Это важно учитывать при интеграции с внешними сервисами.
message: (args) => {
return `Ошибка проверки значения ${args.value}`;
}
Никакие await внутри message не поддерживаются, поэтому
любые внешние данные должны быть заранее загружены в объект.
Функциональный подход к сообщениям в class-validator строится вокруг нескольких принципов:
ValidationArguments как основного
источника контекстаconstraints для параметризацииargs.objectЭта модель позволяет превращать сообщения об ошибках из статических строк в контекстно-зависимый слой, тесно связанный с данными и бизнес-правилами приложения.