Объект ошибок ValidationError

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


Базовая структура ValidationError

При неуспешной валидации возвращается объект ошибки, который обычно имеет следующий вид:

{
  name: 'ValidationError',
  isJoi: true,
  message: '"username" is not allowed to be empty',
  details: [ ... ],
  _original: { ... }
}

Каждое поле имеет строго определённую роль и используется для диагностики и обработки ошибок.


Поле message

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

Пример:

'"email" must be a valid email'

Особенности:

  • может быть переопределено через кастомные сообщения
  • обычно содержит путь до поля и описание нарушения
  • не предназначено для структурной обработки

Поле details

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

Структура одного элемента массива:

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

Поле path

path описывает путь к проблемному значению внутри объекта данных.

Примеры:

  • ['email']
  • ['user', 'profile', 'age']

Используется для:

  • точной локализации ошибки
  • построения пользовательских сообщений
  • автоматической подсветки UI-полей

Поле type

type указывает тип нарушения правила валидации.

Примеры:

  • string.empty
  • string.email
  • number.min
  • array.min
  • any.required

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


Поле context

context содержит дополнительные данные, использованные при проверке правила.

Часто включает:

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

Пример:

context: {
  value: 10,
  limit: 18,
  key: 'age',
  label: 'age'
}

Поле _original

_original содержит исходный объект данных, переданный на валидацию.

{
  username: '',
  age: 16
}

Используется для:

  • логирования
  • отладки
  • повторной обработки данных

Поле isJoi

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

true

Часто применяется в middleware для фильтрации ошибок:

if (error.isJoi) {
  // обработка ошибок валидации
}

Группировка ошибок в details

Массив details позволяет получить сразу несколько ошибок за одну проверку схемы.

Пример:

[
  { path: ['email'], type: 'string.email' },
  { path: ['password'], type: 'string.min' }
]

Это особенно важно при использовании режима abortEarly: false, при котором валидация не останавливается на первой ошибке.


Поведение при abortEarly

Настройка:

Joi.object({
  email: Joi.string().email(),
  password: Joi.string().min(6)
}).validate(data, { abortEarly: false });

Результат:

  • все ошибки собираются в details
  • каждое поле анализируется независимо

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

При использовании messages() структура ValidationError сохраняется, но текстовые поля изменяются.

Joi.string().email().messages({
  'string.email': 'Некорректный формат email'
});

В результате:

  • message изменяется
  • details[].message также обновляется
  • структура path, type, context остаётся неизменной

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

Типичный сценарий обработки:

app.post('/register', (req, res, next) => {
  const schema = Joi.object({
    email: Joi.string().email().required(),
    password: Joi.string().min(6).required()
  });

  const { error } = schema.validate(req.body, { abortEarly: false });

  if (error) {
    return res.status(400).json({
      errors: error.details.map(d => ({
        field: d.path.join('.'),
        message: d.message,
        type: d.type
      }))
    });
  }

  next();
});

Здесь:

  • details преобразуется в формат API
  • path используется как идентификатор поля
  • type помогает классифицировать ошибку

Нормализация ошибок

ValidationError часто преобразуется в унифицированный формат:

const normalized = error.details.reduce((acc, err) => {
  acc[err.path.join('.')] = err.message;
  return acc;
}, {});

Результат:

{
  email: '"email" must be a valid email',
  password: '"password" length must be at least 6 characters long'
}

Поведение при вложенных объектах

При сложных схемах path отражает полную иерархию:

{
  user: {
    profile: {
      age: 15
    }
  }
}

Ошибка:

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

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

  • точно находить поле в UI
  • работать с динамическими формами
  • строить глубокие структуры ошибок

Отличие message и details.message

  • message — агрегированная строка всей ошибки
  • details[].message — конкретное сообщение для каждого нарушения

При множественных ошибках message обычно содержит первую или наиболее значимую.


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

При использовании custom() или external():

Joi.string().custom((value, helpers) => {
  if (value === 'bad') {
    return helpers.error('any.custom');
  }
  return value;
});

ValidationError будет содержать:

  • type: 'any.custom'
  • соответствующий context
  • стандартную структуру details

Особенности сериализации

ValidationError может быть сериализован в JSON, но:

  • методы объекта теряются
  • остаются только данные (message, details, _original)
  • Error.stack не всегда включается

Значение ValidationError для архитектуры приложений

ValidationError используется как:

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

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