При валидации данных библиотека Joi формирует объект ошибки типа
ValidationError, который содержит полную информацию о
нарушениях схемы. Этот объект является ключевой точкой для анализа,
обработки и преобразования результатов валидации.
Основные поля ошибки:
ValidationErrorКаждый элемент массива details представляет собой объект
ValidationErrorItem, содержащий:
string.min)В зависимости от способа вызова 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 позволяет управлять тем, как формируются ошибки, через параметры валидации.
Joi.object({...}).validate(data, { abortEarly: false });
Если значение false, Joi собирает все ошибки, а не
останавливается на первой.
Определяет, будет ли выполняться автоматическое приведение типов перед проверкой.
{ 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() позволяет полностью перехватить и заменить
объект ошибки.
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;
});
Это позволяет:
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: "\""
}
}
});
Это влияет на форматирование путей и текста ошибок.
Часто требуется привести ошибки 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() помогает визуализировать ошибки прямо в
структуре данных:
console.log(err.annotate());
Он возвращает строковое представление объекта с пометками ошибок, что полезно при отладке сложных вложенных структур.
Каждая ошибка содержит context, который можно
использовать для динамической генерации сообщений:
messages({
"string.min": "{{#label}} слишком короткий, минимум {{#limit}}"
});
Переменные из context подставляются автоматически:
labellimitvalueИногда требуется скрыть технические детали:
.error(err => {
return new Error("Ошибка валидации данных");
});
Либо ограничить информацию:
.error(err => {
return new Error(err.details.map(d => d.message).join("; "));
});
При комбинировании messages, error(),
custom() и external() действует приоритет:
external() ошибки (после валидации)error() — полная замена ошибкиmessages() — модификация текстаdetailsВо вложенных объектах пути ошибок могут быть многоуровневыми:
a.b.c
Такие пути используются для:
Для унификации часто фиксируют формат через централизованную функцию:
function normalizeJoiError(err) {
return {
errors: err.details.map(d => ({
path: d.path.join('.'),
message: d.message
}))
};
}
Это позволяет отделить слой валидации от слоя бизнес-логики и представления данных.