В библиотеке class-validator механизм сообщений об
ошибках строится вокруг параметра message, доступного во
всех встроенных декораторах валидации. Он позволяет переопределять
стандартные тексты ошибок и формировать их динамически на основе входных
данных.
Базовый вариант представляет собой строку:
import { IsEmail } from "class-validator";
class User {
@IsEmail({}, { message: "Некорректный формат email-адреса" })
email: string;
}
При нарушении правила валидации будет возвращено указанное сообщение вместо стандартного.
Более гибкий вариант — использование функции, возвращающей строку. Функция получает контекст валидации и позволяет формировать сообщение на основе значения свойства и параметров декоратора.
import { Length } from "class-validator";
class User {
@Length(5, 20, {
message: (args) => {
return `Поле ${args.property} должно содержать от ${args.constraints[0]} до ${args.constraints[1]} символов`;
},
})
username: string;
}
Контекст args содержит ключевую информацию:
property — имя свойстваvalue — текущее значениеconstraints — массив параметров декоратораtargetName — имя классаobject — экземпляр объектаИспользование функции позволяет строить сообщения, учитывающие бизнес-логику и структуру данных.
Тип ValidationArguments является центральным элементом
для построения сложных сообщений. Он предоставляет доступ ко всем
данным, необходимым для анализа ошибки.
import { ValidationArguments } from "class-validator";
Структура контекста позволяет реализовать:
Пример использования:
@Length(10, 50, {
message: (args: ValidationArguments) => {
const valueLength = (args.value as string)?.length ?? 0;
return `${args.property}: текущая длина ${valueLength}, допустимый диапазон ${args.constraints[0]}-${args.constraints[1]}`;
},
})
title: string;
При масштабировании проекта повторяющиеся строки сообщений становятся проблемой. Решением выступает вынос логики формирования сообщений в отдельные функции.
function minMaxMessage(args: ValidationArguments) {
return `${args.property} должен соответствовать диапазону ${args.constraints[0]}-${args.constraints[1]}`;
}
class Product {
@Length(3, 10, { message: minMaxMessage })
code: string;
@Length(5, 100, { message: minMaxMessage })
name: string;
}
Такой подход снижает дублирование и обеспечивает единый формат ошибок.
buildMessage для унификацииБиблиотека предоставляет утилиту buildMessage,
предназначенную для стандартизации генерации сообщений. Она позволяет
описывать шаблон один раз и переиспользовать его для разных полей.
import { buildMessage, ValidationArguments } from "class-validator";
const minMax = buildMessage(
(args: ValidationArguments) =>
`${args.property} должно быть длиной от ${args.constraints[0]} до ${args.constraints[1]}`,
);
class Account {
@minMax(5, 15)
login: string;
@minMax(8, 20)
password: string;
}
Механизм работает как фабрика декораторов, принимающая параметры и возвращающая функцию сообщения.
defaultMessageПри создании собственных валидаторов через
ValidatorConstraint появляется возможность централизованно
управлять сообщениями об ошибках через метод
defaultMessage.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from "class-validator";
@ValidatorConstraint({ name: "isEven", async: false })
class IsEvenConstraint implements ValidatorConstraintInterface {
validate(value: number) {
return typeof value === "number" && value % 2 === 0;
}
defaultMessage(args: ValidationArguments) {
return `Значение ${args.property} должно быть чётным числом`;
}
}
Использование:
import { Validate } from "class-validator";
class NumberModel {
@Validate(IsEvenConstraint)
value: number;
}
Такой подход отделяет логику проверки от логики формирования ошибок, сохраняя чистую архитектуру.
Одним из мощных сценариев является доступ к полному объекту через
args.object. Это позволяет учитывать взаимосвязанные
поля.
@Length(5, 20, {
message: (args: ValidationArguments) => {
const obj = args.object as any;
if (obj.isAdmin) {
return `${args.property} для администратора имеет расширенные ограничения`;
}
return `${args.property} имеет некорректную длину`;
},
})
username: string;
isAdmin: boolean;
Таким образом, сообщение становится зависимым от состояния всей модели, а не только отдельного свойства.
В крупных приложениях часто применяется единый слой генерации ошибок. Он позволяет:
Пример абстракции:
const messages = {
required: (field: string) => `Поле ${field} обязательно`,
length: (field: string, min: number, max: number) =>
`${field} должно содержать от ${min} до ${max} символов`,
};
Использование:
@Length(3, 12, {
message: (args) => messages.length(args.property, 3, 12),
})
code: string;
Поддержка мультиязычности реализуется через внешние словари и функции выбора языка.
const i18n = {
en: {
length: (field: string, min: number, max: number) =>
`${field} must be between ${min} and ${max} characters`,
},
ru: {
length: (field: string, min: number, max: number) =>
`${field} должно быть от ${min} до ${max} символов`,
},
};
let lang: keyof typeof i18n = "ru";
@Length(3, 10, {
message: (args) =>
i18n[lang].length(args.property, args.constraints[0], args.constraints[1]),
})
title: string;
Такая схема позволяет динамически менять язык без изменения логики валидации.
В некоторых архитектурах вместо строк используются структурированные объекты, что удобно для API.
@Length(5, 15, {
message: (args) => {
return JSON.stringify({
field: args.property,
error: "LENGTH_INVALID",
constraints: args.constraints,
});
},
})
username: string;
Это упрощает обработку ошибок на клиентской стороне и позволяет строить универсальные UI-компоненты.
При наследовании классов сообщения могут быть переопределены без изменения базовой логики валидации.
class BaseUser {
@Length(3, 10, { message: "Неверная длина логина" })
username: string;
}
class AdminUser extends BaseUser {
@Length(3, 10, { message: "Администратор: недопустимая длина логина" })
username: string;
}
Такой подход используется для различной интерпретации одних и тех же правил.
Кастомные сообщения часто рассматриваются не как технический слой, а как часть доменной модели. В этом случае сообщения отражают бизнес-термины, а не технические ограничения.
@IsEmail({}, {
message: (args) => `Контактный email "${args.value}" не соответствует корпоративному формату`,
})
email: string;
Такой стиль повышает читаемость ошибок и облегчает их интерпретацию в логике домена.