Обработка ошибок валидации

Тип ошибки ValidationError

В библиотеке Yup все ошибки валидации сводятся к единому типу — ValidationError. Именно этот объект выбрасывается при несоответствии данных схеме и содержит всю необходимую информацию для дальнейшей обработки.

Ключевая особенность модели ошибок Yup заключается в том, что ошибка не является простой строкой. Это структурированный объект, который позволяет анализировать сразу несколько проблем валидации, включая вложенные поля и множественные нарушения правил.

Основные свойства ValidationError:

  • name — всегда ValidationError
  • message — общее сообщение об ошибке
  • path — путь к полю, где возникла ошибка
  • value — исходное значение, которое не прошло проверку
  • inner — массив вложенных ошибок (важно для объектов и массивов)
  • errors — список строковых сообщений ошибок

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

Yup формирует ошибки в виде дерева, особенно при работе с объектами и массивами. Это позволяет точно определить, какое именно поле не прошло проверку.

Пример структуры:

{
  name: "ValidationError",
  message: "Validation failed",
  path: "user.email",
  value: "not-an-email",
  inner: [],
  errors: ["email must be a valid email"]
}

При сложных схемах:

{
  path: "user",
  inner: [
    {
      path: "user.email",
      message: "Invalid email"
    },
    {
      path: "user.password",
      message: "Password is too short"
    }
  ]
}

Поле inner становится ключевым источником информации при массовой обработке ошибок.


Поведение abortEarly

Одним из важных механизмов управления ошибками является параметр abortEarly.

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

schema.validate(data, { abortEarly: true })

Это поведение приводит к тому, что:

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

При отключении:

schema.validate(data, { abortEarly: false })

валидация продолжает выполняться для всех полей, и inner наполняется полным списком ошибок.

Это особенно важно для форм, где требуется показать все ошибки одновременно.


Обработка ошибок через try/catch

Стандартный способ обработки ошибок — использование try/catch при вызове validate.

try {
  await schema.validate(data, { abortEarly: false });
} catch (err) {
  if (err.name === "ValidationError") {
    console.log(err.errors);
  }
}

При синхронной валидации:

try {
  schema.validateSync(data, { abortEarly: false });
} catch (err) {
  console.log(err.errors);
}

Важно учитывать, что validate возвращает Promise, а validateSync выбрасывает исключение сразу.


Асинхронная валидация и ошибки

Yup поддерживает асинхронные проверки через .test() и внешние запросы.

Пример:

const schema = yup.string().test(
  "check-username",
  "Username already exists",
  async (value) => {
    const res = await api.checkUsername(value);
    return res.available;
  }
);

При такой валидации ошибки формируются так же, как и в синхронном режиме, но требуют await при обработке:

try {
  await schema.validate(data);
} catch (err) {
  console.log(err.message);
}

Асинхронные ошибки не отличаются по структуре, но могут увеличивать время полного формирования inner.


Агрегация ошибок для форм

При работе с формами часто требуется преобразовать ValidationError в структуру вида:

{
  email: "Invalid email",
  password: "Too short"
}

Для этого используется обход inner:

function mapYupErrors(err) {
  const errors = {};

  err.inner.forEach((e) => {
    if (e.path) {
      errors[e.path] = e.message;
    }
  });

  return errors;
}

Если abortEarly: true, необходимо учитывать fallback:

if (err.path) {
  errors[err.path] = err.message;
}

Вложенные схемы и path

Yup использует точечную нотацию для вложенных объектов:

const schema = yup.object({
  user: yup.object({
    email: yup.string().email().required()
  })
});

При ошибке путь будет:

user.email

Это позволяет напрямую связывать ошибки с UI-компонентами или структурами состояния.

Для массивов используется индекс:

items[0].name

или в нормализованной форме:

items.0.name

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

Механизм .test() позволяет управлять формированием ошибок вручную.

yup.number().test(
  "is-positive",
  "Value must be positive",
  (value) => value > 0
);

В случае возврата false автоматически создаётся ValidationError с указанным сообщением.

Также возможно динамическое формирование ошибки:

yup.string().test(
  "custom-check",
  function (value) {
    if (!value) {
      return this.createError({ message: "Required field" });
    }
    return true;
  }
);

Использование this.createError позволяет задавать:

  • кастомный message
  • path
  • тип ошибки

Локализация ошибок через setLocale

Yup поддерживает глобальную настройку сообщений об ошибках:

import * as yup from "yup";

yup.setLocale({
  mixed: {
    required: "Поле обязательно"
  },
  string: {
    email: "Некорректный email"
  }
});

После этого все ошибки, не переопределённые вручную, будут использовать локализованные сообщения.

Это влияет на поле message в ValidationError, но не изменяет структуру inner.


Нормализация ошибок для сложных схем

При сложных схемах полезно приводить ошибки к единому формату:

function normalizeErrors(error) {
  return error.inner.reduce((acc, curr) => {
    const path = curr.path || "form";
    acc[path] = acc[path] || [];
    acc[path].push(curr.message);
    return acc;
  }, {});
}

Такой подход позволяет:

  • группировать ошибки по полям
  • хранить несколько сообщений на одно поле
  • упрощать отображение UI

Условная валидация и ошибки (when)

При использовании when() структура ошибок может зависеть от состояния других полей:

yup.string().when("role", {
  is: "admin",
  then: (schema) => schema.required("Admin email required")
});

В таких случаях path остаётся стабильным, но message формируется динамически.

Ошибки могут появляться или исчезать в зависимости от входных данных, что усложняет кеширование результатов валидации.


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

При работе с массивами объектов Yup генерирует множественные ошибки с одинаковым path, но разными индексами:

items[0].name
items[1].name

Для корректной агрегации важно учитывать полный путь:

errors[curr.path] = curr.message;

или преобразовывать в структуру:

{
  items: [
    { name: "Error" },
    { name: "Another error" }
  ]
}

Типичные особенности поведения ValidationError

  • inner заполняется только при abortEarly: false
  • message содержит только первую ошибку при одиночной валидации
  • errors может содержать дубликаты сообщений при сложных схемах
  • path может быть undefined для корневых ошибок
  • асинхронные проверки не изменяют структуру ошибки

Обработка ошибок в цепочках схем

При использовании .concat() или объединении схем ошибки не теряют контекст, но могут дублироваться:

const schema = baseSchema.concat(extraSchema);

В этом случае inner может содержать пересекающиеся path, что требует дедупликации при обработке.


Производственные подходы к обработке ошибок

В прикладных системах часто применяется единый слой обработки:

  • нормализация ValidationError
  • преобразование inner в map
  • кэширование результатов валидации
  • унификация структуры ошибок для UI

Такой слой отделяет Yup от бизнес-логики и упрощает поддержку сложных форм и API-валидаций.