Переопределение стандартных сообщений

В Joi управление сообщениями об ошибках построено на системе ключей, привязанных к конкретным правилам валидации. Каждое стандартное сообщение связано с определённым типом ошибки: несоответствие типа, пустое значение, нарушение ограничения длины, диапазона и так далее. Переопределение этих сообщений выполняется на уровне схемы, отдельных правил или глобальных настроек через механизм messages.

Каждое правило в Joi генерирует ошибку с уникальным кодом. Этот код используется как ключ при замене текста сообщения.

Примеры ключей:

  • string.base — значение не является строкой
  • string.empty — строка пустая
  • string.min — строка короче минимальной длины
  • string.max — строка длиннее допустимого значения
  • any.required — обязательное поле отсутствует
  • number.base — значение не является числом
  • number.min — число меньше допустимого
  • number.max — число больше допустимого

Каждый из этих ключей может быть переопределён независимо от остальных.

Базовое переопределение сообщений через messages

Основной механизм кастомизации сообщений — метод messages(), применяемый к схеме.

import Joi from 'joi';

const schema = Joi.object({
  username: Joi.string()
    .min(3)
    .max(30)
    .required()
    .messages({
      'string.base': 'Имя пользователя должно быть строкой',
      'string.empty': 'Имя пользователя не может быть пустым',
      'string.min': 'Имя пользователя слишком короткое',
      'string.max': 'Имя пользователя слишком длинное',
      'any.required': 'Имя пользователя обязательно'
    })
});

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

Переопределение сообщений на уровне конкретного правила

Joi позволяет задавать сообщения прямо внутри цепочки методов, ограничивая область действия кастомизации.

const schema = Joi.object({
  age: Joi.number()
    .min(18)
    .max(65)
    .messages({
      'number.base': 'Возраст должен быть числом',
      'number.min': 'Возраст не может быть меньше 18 лет',
      'number.max': 'Возраст не может превышать 65 лет'
    })
});

Такой подход делает логику сообщений локальной и привязанной к конкретному полю.

Использование контекста в сообщениях

Joi поддерживает подстановку значений из контекста ошибки. Это позволяет формировать динамические сообщения с использованием параметров правила.

Доступные переменные зависят от типа правила:

  • {#limit} — ограничение (min/max)
  • {#value} — переданное значение
  • {#label} — имя поля

Пример:

const schema = Joi.object({
  password: Joi.string()
    .min(8)
    .messages({
      'string.min': 'Пароль должен содержать не менее {#limit} символов'
    })
});

В момент генерации ошибки {#limit} заменяется на фактическое значение 8.

Переопределение сообщений для нескольких полей

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

const baseStringMessages = {
  'string.base': 'Значение должно быть строкой',
  'string.empty': 'Значение не может быть пустым'
};

const schema = Joi.object({
  firstName: Joi.string().messages(baseStringMessages),
  lastName: Joi.string().messages(baseStringMessages),
  city: Joi.string().messages(baseStringMessages)
});

Такой подход устраняет дублирование и обеспечивает единообразие сообщений.

Глобальные переопределения через preferences

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

const schema = Joi.object({
  email: Joi.string().email().required(),
  phone: Joi.string().required()
}).prefs({
  messages: {
    'string.base': 'Поле должно быть текстовым значением',
    'any.required': 'Обязательное поле отсутствует'
  }
});

При таком подходе локальные messages() могут переопределять глобальные значения, если конфликтуют по ключу.

Приоритеты переопределения сообщений

Система разрешения сообщений работает по уровневой модели:

  1. Сообщения, заданные внутри конкретного правила (messages() у поля)
  2. Сообщения, заданные на уровне схемы через prefs({ messages })
  3. Стандартные сообщения Joi

Более локальный уровень всегда перекрывает глобальный.

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

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

const schema = Joi.object({
  code: Joi.string()
    .length(5)
    .messages({
      'string.base': 'Код должен быть строкой',
      'string.length': 'Код должен содержать ровно 5 символов',
      'any.required': 'Код обязателен'
    })
});

Здесь один валидатор string.length имеет собственный ключ, отличающийся от min и max, поскольку проверяет точное значение.

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

Для поддержки крупных проектов часто создаются централизованные словари сообщений.

const messages = {
  required: {
    'any.required': 'Поле обязательно для заполнения'
  },
  string: {
    'string.base': 'Ожидается строка',
    'string.empty': 'Строка не может быть пустой'
  }
};

const schema = Joi.object({
  login: Joi.string().messages({
    ...messages.string,
    ...messages.required
  })
});

Такой подход позволяет стандартизировать текст ошибок без дублирования логики.

Особенности переопределения сообщений для вложенных объектов

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

const schema = Joi.object({
  user: Joi.object({
    name: Joi.string().required().messages({
      'string.base': 'Имя должно быть строкой',
      'any.required': 'Имя пользователя обязательно'
    })
  })
});

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

Использование шаблонных сообщений

Joi поддерживает шаблонизацию, позволяющую формировать универсальные сообщения для разных типов правил.

const schema = Joi.object({
  score: Joi.number()
    .min(0)
    .max(100)
    .messages({
      'number.min': 'Значение должно быть не меньше {#limit}',
      'number.max': 'Значение должно быть не больше {#limit}'
    })
});

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

Перекрытие сообщений при комбинированных правилах

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

const schema = Joi.object({
  age: Joi.number()
    .integer()
    .min(18)
    .messages({
      'number.base': 'Возраст должен быть числом',
      'number.integer': 'Возраст должен быть целым числом',
      'number.min': 'Минимальный возраст — {#limit}'
    })
});

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

Согласованность сообщений в сложных схемах

При увеличении сложности схемы критическим становится единообразие ключей сообщений. Использование одинаковых ключей (string.base, any.required, number.min) во всех частях схемы позволяет централизованно управлять текстами и избегать расхождений в поведении валидатора.

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