Детализация ошибок

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


Структура объекта ошибки

При неудачной валидации метод validate() возвращает объект с полем error.

const Joi = require('joi');

const schema = Joi.object({
    username: Joi.string().min(5).required()
});

const result = schema.validate({
    username: 'abc'
});

console.log(result.error);

Вывод:

[Error [ValidationError]: "username" length must be at least 5 characters long]

Основные свойства объекта ошибки:

Свойство Описание
message Текстовое описание ошибки
details Массив детализированных ошибок
_original Исходные данные
annotate() Форматированный вывод ошибки

Свойство error.details

Наиболее важная часть ошибки — массив details.

console.log(result.error.details);

Пример:

[
  {
    message: '"username" length must be at least 5 characters long',
    path: ['username'],
    type: 'string.min',
    context: {
      limit: 5,
      value: 'abc',
      label: 'username',
      key: 'username'
    }
  }
]

Поля объекта details

message

Человекочитаемое описание ошибки.

detail.message

Пример:

'"username" length must be at least 5 characters long'

path

Путь к полю, вызвавшему ошибку.

detail.path

Пример:

['profile', 'email']

Можно преобразовать в строку:

detail.path.join('.')

Результат:

profile.email

type

Тип ошибки.

detail.type

Примеры:

Тип Значение
string.min Строка слишком короткая
string.max Строка слишком длинная
any.required Поле обязательно
number.base Ожидалось число
array.min Недостаточно элементов
object.unknown Неизвестное поле

context

Дополнительная информация об ошибке.

detail.context

Пример:

{
  limit: 5,
  value: 'abc',
  label: 'username',
  key: 'username'
}

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

По умолчанию Joi прекращает проверку после первой ошибки.

const schema = Joi.object({
    username: Joi.string().min(5).required(),
    age: Joi.number().min(18)
});

const result = schema.validate({
    username: 'ab',
    age: 10
});

console.log(result.error.details);

Будет возвращена только первая ошибка.


Опция abortEarly

Для получения всех ошибок используется abortEarly: false.

const result = schema.validate(
    {
        username: 'ab',
        age: 10
    },
    {
        abortEarly: false
    }
);

Результат:

[
  {
    message: '"username" length must be at least 5 characters long'
  },
  {
    message: '"age" must be greater than or equal to 18'
  }
]

Формирование собственного списка ошибок

Часто требуется преобразовать ошибки Joi в удобный формат API.

Пример преобразования

const errors = result.error.details.map(detail => ({
    field: detail.path.join('.'),
    message: detail.message,
    type: detail.type
}));

console.log(errors);

Результат:

[
  {
    field: 'username',
    message: '"username" length must be at least 5 characters long',
    type: 'string.min'
  },
  {
    field: 'age',
    message: '"age" must be greater than or equal to 18',
    type: 'number.min'
  }
]

Настройка текстов ошибок

Метод messages()

Joi позволяет переопределять стандартные сообщения.

const schema = Joi.object({
    username: Joi.string()
        .min(5)
        .required()
        .messages({
            'string.min': 'Минимальная длина имени — 5 символов',
            'any.required': 'Имя обязательно'
        })
});

Коды ошибок

Каждое сообщение связано с кодом.

Примеры кодов

Код Описание
string.empty Пустая строка
string.email Некорректный email
number.min Число меньше минимального
array.max Слишком много элементов
date.base Некорректная дата
object.base Ожидался объект

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

В сообщениях можно использовать переменные.

const schema = Joi.string().min(5).messages({
    'string.min': 'Минимум {#limit} символов'
});

Результат:

Минимум 5 символов

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

Joi не имеет встроенной полноценной i18n-системы, но позволяет создавать собственные словари сообщений.

const messages = {
    'string.empty': 'Поле не должно быть пустым',
    'string.email': 'Некорректный email'
};

const schema = Joi.string().email().messages(messages);

Глобальная настройка сообщений

Сообщения можно задавать через preferences.

const schema = Joi.object({
    email: Joi.string().email()
}).prefs({
    messages: {
        'string.email': 'Неверный формат email'
    }
});

Метод error()

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

const schema = Joi.string().min(5).error(
    new Error('Неверное имя пользователя')
);

Динамическое формирование ошибок

В error() можно передать функцию.

const schema = Joi.string().min(5).error(errors => {
    return new Error(`Ошибка: ${errors[0].message}`);
});

suppress stack trace

Иногда стек ошибок не нужен.

const result = schema.validate(data, {
    errors: {
        stack: false
    }
});

HTML escaping

Joi умеет экранировать HTML.

const schema = Joi.string().messages({
    'string.base': 'Поле {{#label}} должно быть строкой'
});

Опции:

{
    errors: {
        escapeHtml: true
    }
}

Форматирование labels

По умолчанию Joi использует имя поля в кавычках.

"username" is required

Настройка:

{
    errors: {
        label: 'key'
    }
}

Возможные значения:

Значение Результат
path Полный путь
key Только ключ
false Без label

annotate()

Метод annotate() создаёт форматированный вывод.

console.log(result.error.annotate());

Пример:

{
  "username" [1]: "ab"
}

[1] "username" length must be at least 5 characters long

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


Ошибки вложенных объектов

const schema = Joi.object({
    profile: Joi.object({
        email: Joi.string().email().required()
    })
});

Ошибка:

{
    path: ['profile', 'email']
}

Получение полного пути:

detail.path.join('.')

Результат:

profile.email

Ошибки массивов

const schema = Joi.array().items(
    Joi.string().min(3)
);

Проверка:

schema.validate(['ok', 'a']);

Ошибка:

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

Ошибки внутри массива объектов

const schema = Joi.array().items(
    Joi.object({
        name: Joi.string().required()
    })
);

Ошибка:

{
    path: [0, 'name']
}

Путь:

0.name

stripUnknown и ошибки неизвестных полей

По умолчанию лишние поля вызывают ошибку только при использовании .unknown(false).

const schema = Joi.object({
    name: Joi.string()
}).unknown(false);

Проверка:

{
    name: 'Alex',
    role: 'admin'
}

Ошибка:

'"role" is not allowed'

Автоматическое удаление неизвестных полей

const result = schema.validate(data, {
    stripUnknown: true
});

Проверка presence

Глобальная обязательность полей:

const result = schema.validate(data, {
    presence: 'required'
});

Теперь все поля обязательны.


Предупреждения warning

Joi поддерживает warnings вместо ошибок.

const schema = Joi.string().warning('string.warning');

Получение предупреждений:

const result = schema.validate('abc', {
    warnings: true
});

console.log(result.warning);

Кастомные ошибки через custom()

const schema = Joi.string().custom((value, helpers) => {

    if (value.includes('admin')) {
        return helpers.error('string.invalidRole');
    }

    return value;
});

Сообщения:

.messages({
    'string.invalidRole': 'Недопустимое имя'
});

helpers.message()

Можно вернуть сообщение напрямую.

const schema = Joi.string().custom((value, helpers) => {

    if (value.length < 10) {
        return helpers.message('Слишком короткое значение');
    }

    return value;
});

Кастомный context

return helpers.error('custom.invalid', {
    min: 10,
    current: value.length
});

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

.messages({
    'custom.invalid': 'Минимум {#min}, сейчас {#current}'
});

errors.wrap

Настройка обёртки label.

{
    errors: {
        wrap: {
            label: ''
        }
    }
}

Было:

"username" is required

Стало:

username is required

Проверка даты и детализация ошибок

const schema = Joi.date().greater('now');

Ошибка:

{
    type: 'date.greater'
}

Проверка альтернатив alternatives()

const schema = Joi.alternatives().try(
    Joi.string(),
    Joi.number()
);

Ошибка:

{
    type: 'alternatives.types'
}

Детализация ошибок conditional validation

const schema = Joi.object({
    role: Joi.string(),

    permissions: Joi.when('role', {
        is: 'admin',
        then: Joi.array().required()
    })
});

Ошибка:

{
    type: 'any.required',
    path: ['permissions']
}

Валидация с convert: false

По умолчанию Joi преобразует типы.

Joi.number().validate('123');

Результат:

123

Отключение:

const result = Joi.number().validate('123', {
    convert: false
});

Ошибка:

{
    type: 'number.base'
}

Отладка сложных схем

При работе со сложными схемами полезны:

abortEarly: false
annotate()
details
path
context

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