Прослушивание и модификация ошибок

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

Основные поля ошибки:

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

Каждый элемент массива details представляет собой объект ValidationErrorItem, содержащий:

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

Перехват ошибок при валидации

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

try {
  Joi.object({
    name: Joi.string().min(3).required()
  }).validate({ name: "A" });
} catch (err) {
  console.log(err.details);
}

Асинхронный вариант:

await schema.validateAsync(data);

При использовании validateAsync ошибка выбрасывается как исключение Promise.


Управление поведением формирования ошибок

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

abortEarly

Joi.object({...}).validate(data, { abortEarly: false });

Если значение false, Joi собирает все ошибки, а не останавливается на первой.

convert

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

{ convert: false }

Кастомизация текстов ошибок

Одним из ключевых механизмов модификации является метод messages.

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

Каждый ключ соответствует типу ошибки (type из details).

Также можно задавать глобальные шаблоны:

Joi.object({
  password: Joi.string().min(8)
}).messages({
  "string.min": "Слишком короткое значение"
});

Полная замена ошибки через error()

Метод error() позволяет полностью перехватить и заменить объект ошибки.

const schema = Joi.string().error(errors => {
  return new Error("Полностью кастомная ошибка");
});

Функция получает массив ошибок и должна вернуть новый объект Error.

Вариант с сохранением структуры:

.error(errors => {
  return new Error(errors[0].message);
});

Глубокая модификация деталей ошибки

При необходимости можно трансформировать не сам объект ошибки, а его details.

const schema = Joi.object({
  name: Joi.string().min(3)
}).error(err => {
  err.details = err.details.map(d => ({
    ...d,
    message: `[VALIDATION FAILED] ${d.message}`
  }));
  return err;
});

Это позволяет:

  • добавлять префиксы
  • изменять формат сообщений
  • фильтровать ошибки
  • объединять несколько сообщений в одно

Обработка внешних ошибок (external validation)

Joi поддерживает асинхронные внешние проверки через .external().

const schema = Joi.string().external(async (value) => {
  if (value === "forbidden") {
    throw new Error("Недопустимое значение");
  }
});

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

try {
  await schema.validateAsync(value);
} catch (err) {
  console.log(err.message);
}

Пользовательские правила и генерация ошибок

При создании кастомных правил через .custom() можно явно формировать ошибки:

const schema = Joi.string().custom((value, helpers) => {
  if (value === "bad") {
    return helpers.error("any.invalid");
  }
  return value;
});

Также можно передавать собственное сообщение:

helpers.message("Недопустимое значение");

Глобальная настройка поведения ошибок

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

const schema = Joi.object({...}).prefs({
  errors: {
    wrap: {
      label: "\""
    }
  }
});

Это влияет на форматирование путей и текста ошибок.


Нормализация и форматирование ошибок для API

Часто требуется привести ошибки Joi к унифицированному формату.

const formatted = err.details.map(e => ({
  field: e.path.join('.'),
  message: e.message,
  type: e.type
}));

Результат используется в ответах API:

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

Работа с несколькими ошибками одновременно

При отключённом abortEarly массив details может содержать множество ошибок:

err.details.forEach(error => {
  console.log(error.path, error.message);
});

Это позволяет строить агрегированные отчёты о валидации, где фиксируются все проблемные поля одновременно.


Изменение поведения через annotate()

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

console.log(err.annotate());

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


Контроль формата сообщений через context

Каждая ошибка содержит context, который можно использовать для динамической генерации сообщений:

messages({
  "string.min": "{{#label}} слишком короткий, минимум {{#limit}}"
});

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

  • label
  • limit
  • value

Программное подавление и преобразование ошибок

Иногда требуется скрыть технические детали:

.error(err => {
  return new Error("Ошибка валидации данных");
});

Либо ограничить информацию:

.error(err => {
  return new Error(err.details.map(d => d.message).join("; "));
});

Особенности последовательной обработки ошибок

При комбинировании messages, error(), custom() и external() действует приоритет:

  1. external() ошибки (после валидации)
  2. error() — полная замена ошибки
  3. messages() — модификация текста
  4. стандартные details

Модификация ошибок в сложных схемах

Во вложенных объектах пути ошибок могут быть многоуровневыми:

a.b.c

Такие пути используются для:

  • точного указания поля
  • построения UI-форм
  • динамической подсветки ошибок

Стабилизация формата ошибок

Для унификации часто фиксируют формат через централизованную функцию:

function normalizeJoiError(err) {
  return {
    errors: err.details.map(d => ({
      path: d.path.join('.'),
      message: d.message
    }))
  };
}

Это позволяет отделить слой валидации от слоя бизнес-логики и представления данных.