Переопределение сообщений через параметр message

Валидационные ошибки в прикладных системах редко остаются внутри серверной логики — почти всегда они доходят до пользователя или до слоя интеграции. Именно поэтому стандартные сообщения, предоставляемые библиотекой по умолчанию, часто оказываются недостаточными: они либо слишком общие, либо не соответствуют бизнес-языку предметной области. В таких случаях используется механизм переопределения сообщений через параметр message.


Базовый принцип работы параметра message

В библиотеке class-validator каждая валидирующая декорация поддерживает опциональный параметр message. Он позволяет задать собственный текст ошибки, который будет возвращён вместо стандартного.

Общий принцип:

  • если message не указан — используется системное сообщение;
  • если message указан — он полностью заменяет стандартный текст;
  • message может быть строкой или функцией.

Простейшая форма:

import { IsString } from "class-validator";

class User {
  @IsString({ message: "Имя должно быть строкой" })
  name: string;
}

В этом случае при нарушении правила IsString пользователь получит сообщение:

Имя должно быть строкой

Строковое сообщение и его особенности

Строковый вариант является наиболее простым и часто используемым. Он подходит для фиксированных текстов, не зависящих от входных данных.

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

import { IsEmail } from "class-validator";

class Account {
  @IsEmail({}, { message: "Некорректный формат email" })
  email: string;
}

Важно учитывать, что строка:

  • не имеет доступа к значению поля;
  • не может учитывать контекст валидации;
  • используется как статический текст.

Это делает её подходящей для простых правил, но ограничивает гибкость.


Функциональное сообщение как инструмент контекстной генерации

Более мощный вариант — передача функции в message. Такая функция получает объект ValidationArguments, который позволяет формировать динамические сообщения.

Пример:

import { MinLength } from "class-validator";

class User {
  @MinLength(8, {
    message: (args) => {
      return `Поле ${args.property} должно содержать минимум ${args.constraints[0]} символов`;
    },
  })
  password: string;
}

Здесь используются ключевые элементы:

  • args.property — имя поля;
  • args.constraints — массив параметров декоратора;
  • args.value — текущее значение поля.

Результат при ошибке:

Поле password должно содержать минимум 8 символов

Структура объекта ValidationArguments

Функциональный message опирается на структуру аргументов:

  • value — текущее значение поля;
  • constraints — параметры, переданные в декоратор;
  • targetName — имя класса;
  • property — имя свойства;
  • object — экземпляр проверяемого объекта.

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

import { Max } from "class-validator";

class Product {
  @Max(1000, {
    message: (args) => {
      return `Значение поля ${args.property} (${args.value}) превышает допустимый максимум ${args.constraints[0]}`;
    },
  })
  price: number;
}

При ошибке:

Значение поля price (1500) превышает допустимый максимум 1000

Использование message для унификации бизнес-логики

В сложных системах сообщения часто стандартизируются. Вместо хаотичного текста создаются шаблоны, отражающие бизнес-терминологию.

Пример:

const REQUIRED_MESSAGE = (field: string) =>
  `Поле ${field} обязательно для заполнения`;

class Profile {
  @IsString({ message: REQUIRED_MESSAGE("firstName") })
  firstName: string;

  @IsString({ message: REQUIRED_MESSAGE("lastName") })
  lastName: string;
}

Такой подход обеспечивает:

  • единообразие сообщений;
  • централизованное управление текстами;
  • упрощение локализации.

Локализация сообщений через message

Одно из распространённых применений — мультиязычность. Параметр message становится точкой интеграции с системой переводов.

Пример с условной функцией перевода:

function t(key: string): string {
  const dictionary = {
    "validation.email": "Некорректный email",
    "validation.required": "Поле обязательно",
  };

  return dictionary[key];
}

class Login {
  @IsEmail({}, { message: () => t("validation.email") })
  email: string;
}

Здесь:

  • логика перевода отделена от валидатора;
  • сообщение не зашито в декоратор напрямую;
  • возможно расширение на любые языки.

Сложные сценарии формирования сообщений

Функция message позволяет реализовать условную логику.

Пример с проверкой диапазона:

import { Min, Max } from "class-validator";

class Sensor {
  @Min(10, {
    message: (args) => {
      if (args.value < 0) {
        return "Значение не может быть отрицательным";
      }
      return `Минимальное допустимое значение: ${args.constraints[0]}`;
    },
  })
  temperature: number;
}

Такой подход позволяет:

  • различать типы ошибок внутри одного правила;
  • адаптировать текст под значение;
  • создавать более информативные сообщения.

Переиспользование логики сообщений

При масштабировании системы появляется необходимость отделять логику сообщений от декораторов.

Пример фабрики сообщений:

function minLengthMessage(field: string, length: number) {
  return (args) =>
    `Поле ${field} должно быть не короче ${length} символов`;
}

class Register {
  @MinLength(6, {
    message: minLengthMessage("username", 6),
  })
  username: string;
}

Преимущества:

  • повторное использование;
  • снижение дублирования;
  • единая точка изменения логики.

Ограничения и особенности поведения

При использовании message важно учитывать несколько особенностей:

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

Также важно учитывать, что:

  • асинхронные операции внутри message не поддерживаются;
  • сообщение должно быть синхронным результатом.

Комбинирование нескольких декораторов с индивидуальными сообщениями

Часто одно поле имеет несколько ограничений:

import { IsString, MinLength, MaxLength } from "class-validator";

class Comment {
  @IsString({ message: "Комментарий должен быть строкой" })
  @MinLength(10, { message: "Слишком короткий комментарий" })
  @MaxLength(200, { message: "Комментарий слишком длинный" })
  text: string;
}

Каждое правило формирует собственное сообщение, что позволяет:

  • точно определять причину ошибки;
  • разделять ответственность между валидаторами;
  • улучшать читаемость ответов API.

Практика проектирования сообщений

При проектировании системы сообщений важно придерживаться нескольких принципов:

  • сообщения должны быть ориентированы на бизнес-термины, а не технические;
  • формулировки должны быть краткими и однозначными;
  • одинаковые ошибки должны иметь одинаковый текст;
  • динамические сообщения использовать только там, где есть смысл в контексте.

Пример различия подходов:

Плохо:

Validation failed

Хорошо:

Поле email должно быть корректным адресом электронной почты

Ещё лучше (контекстно):

Значение "test@" не является допустимым email

Использование message в сложных доменных моделях

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

Пример:

class BankAccount {
  @Min(0, {
    message: (args) =>
      `Баланс счета не может быть отрицательным: текущий ${args.value}`,
  })
  balance: number;
}

Здесь сообщение уже несёт смысловое значение, связанное с доменом, а не с технической проверкой.


Централизация сообщений через константы и модули

При увеличении количества сущностей возникает необходимость централизовать сообщения:

export const ValidationMessages = {
  REQUIRED: "Поле обязательно для заполнения",
  INVALID_EMAIL: "Некорректный email адрес",
  TOO_SHORT: (min: number) => `Минимальная длина: ${min}`,
};

Использование:

@IsEmail({}, { message: ValidationMessages.INVALID_EMAIL })
email: string;

Такой подход упрощает сопровождение и уменьшает вероятность расхождений в тексте.