Механизм обработки ошибок в 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() |
Форматированный вывод ошибки |
Наиболее важная часть ошибки — массив 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'
}
}
]
Человекочитаемое описание ошибки.
detail.message
Пример:
'"username" length must be at least 5 characters long'
Путь к полю, вызвавшему ошибку.
detail.path
Пример:
['profile', 'email']
Можно преобразовать в строку:
detail.path.join('.')
Результат:
profile.email
Тип ошибки.
detail.type
Примеры:
| Тип | Значение |
|---|---|
string.min |
Строка слишком короткая |
string.max |
Строка слишком длинная |
any.required |
Поле обязательно |
number.base |
Ожидалось число |
array.min |
Недостаточно элементов |
object.unknown |
Неизвестное поле |
Дополнительная информация об ошибке.
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: 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'
}
]
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() полностью заменяет объект ошибки.
const schema = Joi.string().min(5).error(
new Error('Неверное имя пользователя')
);
В error() можно передать функцию.
const schema = Joi.string().min(5).error(errors => {
return new Error(`Ошибка: ${errors[0].message}`);
});
Иногда стек ошибок не нужен.
const result = schema.validate(data, {
errors: {
stack: false
}
});
Joi умеет экранировать HTML.
const schema = Joi.string().messages({
'string.base': 'Поле {{#label}} должно быть строкой'
});
Опции:
{
errors: {
escapeHtml: true
}
}
По умолчанию Joi использует имя поля в кавычках.
"username" is required
Настройка:
{
errors: {
label: 'key'
}
}
Возможные значения:
| Значение | Результат |
|---|---|
path |
Полный путь |
key |
Только ключ |
false |
Без label |
Метод 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
По умолчанию лишние поля вызывают ошибку только при использовании
.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
});
Глобальная обязательность полей:
const result = schema.validate(data, {
presence: 'required'
});
Теперь все поля обязательны.
Joi поддерживает warnings вместо ошибок.
const schema = Joi.string().warning('string.warning');
Получение предупреждений:
const result = schema.validate('abc', {
warnings: true
});
console.log(result.warning);
const schema = Joi.string().custom((value, helpers) => {
if (value.includes('admin')) {
return helpers.error('string.invalidRole');
}
return value;
});
Сообщения:
.messages({
'string.invalidRole': 'Недопустимое имя'
});
Можно вернуть сообщение напрямую.
const schema = Joi.string().custom((value, helpers) => {
if (value.length < 10) {
return helpers.message('Слишком короткое значение');
}
return value;
});
return helpers.error('custom.invalid', {
min: 10,
current: value.length
});
Использование:
.messages({
'custom.invalid': 'Минимум {#min}, сейчас {#current}'
});
Настройка обёртки label.
{
errors: {
wrap: {
label: ''
}
}
}
Было:
"username" is required
Стало:
username is required
const schema = Joi.date().greater('now');
Ошибка:
{
type: 'date.greater'
}
const schema = Joi.alternatives().try(
Joi.string(),
Joi.number()
);
Ошибка:
{
type: 'alternatives.types'
}
const schema = Joi.object({
role: Joi.string(),
permissions: Joi.when('role', {
is: 'admin',
then: Joi.array().required()
})
});
Ошибка:
{
type: 'any.required',
path: ['permissions']
}
По умолчанию Joi преобразует типы.
Joi.number().validate('123');
Результат:
123
Отключение:
const result = Joi.number().validate('123', {
convert: false
});
Ошибка:
{
type: 'number.base'
}
При работе со сложными схемами полезны:
abortEarly: false
annotate()
details
path
context
Комбинация этих инструментов позволяет получать полную диагностическую информацию о проблемах структуры данных, типах, ограничениях и вложенных объектах.