Обработка результатов валидации

При вызове schema.validate(value, options) библиотека возвращает объект, содержащий как минимум два ключевых поля: value и error. Именно работа с этими двумя сущностями определяет весь дальнейший контроль над входными данными.

Базовая структура результата

Типичный результат валидации выглядит следующим образом:

const result = schema.validate(data);

console.log(result);
{
  value: { ... },   // преобразованные (или исходные) данные
  error: null       // либо объект ошибки, либо null
}

Если данные соответствуют схеме, поле error будет равно null, а value может отличаться от исходного объекта в зависимости от настроек преобразования.


Поле value: результат после преобразований

value — это не просто входные данные. Joi может их модифицировать:

  • приведение типов ("123"123)
  • применение значений по умолчанию
  • удаление лишних полей (при stripUnknown)
  • нормализация строк (trim, lowercase и т.д.)

Пример:

const schema = Joi.object({
  age: Joi.number().default(18),
  name: Joi.string().trim()
});

const { value } = schema.validate({ name: "  Alex  " });

Результат:

{
  name: "Alex",
  age: 18
}

Ключевой момент: value — это итоговая безопасная версия данных, пригодная для дальнейшей работы.


Поле error: структура ошибки валидации

Если валидация не прошла, error содержит объект типа ValidationError.

const { error } = schema.validate({ age: "abc" });

Структура ошибки:

{
  name: "ValidationError",
  details: [
    {
      message: "\"age\" must be a number",
      path: ["age"],
      type: "number.base",
      context: {
        label: "age",
        value: "abc",
        key: "age"
      }
    }
  ]
}

Поле details

details — это массив, содержащий все найденные нарушения схемы. Каждый элемент описывает одну проблему.

Основные поля:

  • message — текст ошибки
  • path — путь к полю (важно для вложенных объектов)
  • type — код ошибки Joi
  • context — дополнительные данные для формирования сообщений

abortEarly и управление количеством ошибок

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

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

Чтобы получить полный список ошибок:

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

Разница:

  • true → одна ошибка
  • false → массив всех ошибок

Практическое значение: режим false используется для отображения форм с множественными подсказками.


Преобразование ошибок для UI

Часто details не подходит напрямую для фронтенда. Обычно требуется нормализация:

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

Результат:

{
  age: "\"age\" must be a number"
}

Для вложенных объектов:

path: ["user", "email"]

Превращается в:

user.email

Игнорирование или переопределение сообщений

Joi позволяет кастомизировать сообщения:

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

При ошибке:

{
  message: "Возраст должен быть числом"
}

Это особенно важно для:

  • интернационализации
  • унифицированного UX
  • сокращения технических сообщений

validate vs validateAsync: различия в обработке результата

Синхронная валидация:

const { value, error } = schema.validate(data);

Асинхронная:

try {
  const value = await schema.validateAsync(data);
} catch (error) {
  console.log(error.details);
}

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

  • при ошибке выбрасывается исключение
  • результат возвращается напрямую
  • удобна для async/await цепочек

Работа с throw-ошибками и try/catch

При использовании validateAsync обработка строго через исключения:

try {
  const user = await schema.validateAsync(req.body);
} catch (err) {
  return res.status(400).json({
    errors: err.details
  });
}

Важно учитывать:

  • err — это не строка
  • err.details всегда массив
  • структура совпадает с синхронной валидацией

Использование в middleware (например, Express)

Типовой шаблон:

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

  if (error) {
    return res.status(400).json({
      errors: error.details
    });
  }

  req.body = value;
  next();
};

Ключевая идея:

  • value заменяет входные данные
  • error останавливает выполнение запроса

stripUnknown и влияние на результат

Опция stripUnknown изменяет итоговый value, удаляя лишние поля:

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

const { value } = schema.validate(
  { name: "Alex", role: "admin" },
  { stripUnknown: true }
);

Результат:

{ name: "Alex" }

Это влияет не на error, а именно на итоговый объект.


Детализация error.type и системная обработка

Поле type позволяет строить логику обработки:

if (error.details[0].type === "string.empty") {
  // специальная обработка пустых строк
}

Примеры типов:

  • string.base
  • number.min
  • any.required
  • object.unknown

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

  • централизованной обработки ошибок
  • генерации кодов ошибок API
  • построения UI-валидации

Нормализация результата валидации

На практике результат Joi часто приводят к единому формату:

{
  success: boolean,
  data: object | null,
  errors: object | null
}

Пример:

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

return {
  success: !result.error,
  data: result.value,
  errors: result.error ? formatErrors(result.error) : null
};

Особенности работы с вложенными структурами

Joi сохраняет точную навигацию по объекту через path:

{
  user: {
    profile: {
      email: "invalid"
    }
  }
}

Ошибка:

path: ["user", "profile", "email"]

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

  • точно указывать поле в интерфейсе
  • строить динамические формы
  • синхронизировать backend и frontend ошибки

Поведение при множественных схемах (alternatives)

При использовании Joi.alternatives() ошибка может содержать агрегированную информацию:

  • несколько вариантов проверки
  • объединённые details
  • более сложную структуру контекста

Это требует дополнительной фильтрации перед выводом пользователю.


Практическая стратегия обработки результата

Типовой алгоритм:

  1. Выполнить validate
  2. Проверить error
  3. При наличии ошибок — извлечь details
  4. Преобразовать ошибки в формат API/UI
  5. Использовать value как единственный источник доверенных данных

Этот подход делает Joi не просто валидатором, а полноценным механизмом подготовки данных к дальнейшей обработке.