Сообщения об ошибках

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


Структура объекта ошибки валидации

При провале проверки Joi возвращает объект ValidationError, который является расширением стандартного объекта Error. Он содержит несколько ключевых элементов, определяющих диагностику:

Основные поля ValidationError:

  • name — всегда "ValidationError", используется для идентификации типа ошибки
  • message — агрегированное текстовое описание всех ошибок
  • details — массив объектов, описывающих каждую конкретную ошибку
  • annotate — вспомогательная функция для визуализации пути ошибки в структуре данных
  • _original — исходные данные, переданные на валидацию
  • isJoi — признак принадлежности к Joi

Объект details как основа диагностики

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

Структура элемента details:

  • message — текст сообщения об ошибке
  • path — путь к полю, где произошла ошибка (массив ключей)
  • type — тип нарушения (например, string.min, number.base)
  • context — контекст ошибки (ожидаемые значения, лимиты, метаданные)

Пример типичного объекта:

{
  message: '"age" must be greater than or equal to 18',
  path: ['age'],
  type: 'number.min',
  context: {
    limit: 18,
    value: 16,
    label: 'age',
    key: 'age'
  }
}

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

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

Часто встречающиеся типы:

  • any.required — обязательное поле отсутствует
  • string.base — значение не является строкой
  • string.min / string.max — нарушение длины строки
  • number.min / number.max — нарушение числовых границ
  • number.base — значение не является числом
  • array.min / array.max — нарушение длины массива
  • object.unknown — обнаружено запрещённое поле

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


Управление поведением генерации ошибок

abortEarly

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

validate(data, { abortEarly: false })

При false формируется полный список ошибок, а не только первая.


allowUnknown и stripUnknown

Эти параметры влияют не только на структуру данных, но и на потенциальные ошибки:

  • allowUnknown: false — запрещает поля, не описанные в схеме
  • stripUnknown: true — удаляет лишние поля без генерации ошибки

Кастомизация сообщений

Joi предоставляет механизм переопределения стандартных сообщений через объект messages().

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

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


Глобальные сообщения

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

const schema = Joi.object({
  username: Joi.string().required(),
  age: Joi.number().required()
}).messages({
  'any.required': 'Поле обязательно для заполнения'
});

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


Переопределение label и path

Joi использует label для формирования человекочитаемых сообщений. Его можно изменить:

Joi.string().label('Имя пользователя')

После этого сообщения об ошибке будут содержать более понятное имя поля.


Функция annotate

Метод annotate() позволяет получить визуальное представление ошибки с указанием пути:

error.annotate()

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


Ошибки в вложенных структурах

При работе с объектами и массивами Joi формирует путь ошибки через массив path.

Пример:

{
  user: {
    profile: {
      age: 16
    }
  }
}

Ошибка будет содержать:

path: ['user', 'profile', 'age']

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


Альтернативные ветви и alternatives errors

При использовании alternatives() возможны составные ошибки, когда ни одна из ветвей не подходит.

Joi.alternatives().try(
  Joi.string(),
  Joi.number()
)

Если оба варианта не проходят, Joi формирует обобщённое сообщение с указанием всех попыток.


Детализация через context

Поле context содержит динамические данные, использованные при проверке:

  • limit — ограничение (например, минимальная длина)
  • value — фактическое значение
  • key — имя поля
  • label — отображаемое имя
  • peers — для зависимых правил (valid(), invalid())

Контекст используется при формировании финального текста сообщения.


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

Joi применяет несколько уровней формирования ошибок:

  1. Проверка базового типа (string, number, object)
  2. Проверка обязательности (required)
  3. Проверка ограничений (min, max, pattern)
  4. Проверка кастомных правил (custom)
  5. Постобработка сообщений (messages)

Каждый этап может перезаписать или дополнить сообщение предыдущего.


Пользовательские ошибки через custom

Функция custom() позволяет вручную формировать ошибки:

Joi.number().custom((value, helpers) => {
  if (value < 0) {
    return helpers.error('number.negative');
  }
  return value;
});

Определение текста затем выполняется через messages():

messages({
  'number.negative': 'Число не может быть отрицательным'
});

Ошибки при преобразовании значений

Joi выполняет преобразования типов (например, строка → число). При неудаче возникает ошибка type casting.

Пример:

  • вход: "abc"
  • схема: Joi.number()

Результат:

  • number.base

Комплексные сообщения валидации

При множественных ошибках Joi агрегирует сообщения в одно:

message: '"age" must be greater than 18. "name" is required'

Порядок зависит от параметра abortEarly и внутренней сортировки details.


Использование errorMap и обёрток

В некоторых архитектурах Joi интегрируется через слой обёртки, который преобразует ошибки в унифицированный формат API:

  • код ошибки
  • человекочитаемый текст
  • поле
  • метаданные

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


Поведение при строгой и нестрогой валидации

Режим strict() отключает преобразование типов, что увеличивает количество ошибок type.base.

Joi.number().strict()

В этом режиме строковые числа больше не приводятся к числам автоматически.


Особенности сообщений в массивах и объектах

При валидации массивов ошибки могут относиться к конкретному индексу:

path: ['items', 2]

Это позволяет точно определить элемент, нарушивший правило.


Влияние schema description на сообщения

Метод describe() не влияет на ошибки напрямую, но используется для анализа схем, из которых могут генерироваться документационные сообщения или UI-описания форм.


Стратегии нормализации ошибок

В реальных приложениях часто выполняется преобразование Joi ошибок в унифицированный формат:

  • группировка по полям
  • извлечение первого сообщения
  • локализация текста
  • замена кодов типов на бизнес-коды

Такая нормализация строится на основе details, поскольку именно он содержит полную информацию о нарушениях.