Кастомные сообщения об ошибках

В библиотеке 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

Тип ValidationArguments является центральным элементом для построения сложных сообщений. Он предоставляет доступ ко всем данным, необходимым для анализа ошибки.

import { ValidationArguments } from "class-validator";

Структура контекста позволяет реализовать:

  • формирование сообщений с учетом значения поля
  • отображение допустимых диапазонов
  • ссылку на соседние поля объекта
  • генерацию унифицированных ошибок для API

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

@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;

Таким образом, сообщение становится зависимым от состояния всей модели, а не только отдельного свойства.


Централизованная система сообщений

В крупных приложениях часто применяется единый слой генерации ошибок. Он позволяет:

  • стандартизировать формат сообщений
  • внедрить локализацию
  • унифицировать API ошибок

Пример абстракции:

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;

Такой стиль повышает читаемость ошибок и облегчает их интерпретацию в логике домена.