Программная обработка ошибок

В основе работы YupResolver лежит преобразование результата валидации схемы Yup в единый формат ошибок, который понимает React Hook Form. Программная обработка ошибок строится вокруг нормализации структуры ValidationError в объект вида:

  • errors: объект с ключами полей формы
  • values: валидированные данные (или частично валидированные)
  • criteriaMode: режим группировки ошибок

Ключевая задача резолвера — превратить сложную, вложенную иерархию ошибок Yup в плоскую структуру, пригодную для UI-рендеринга и программной обработки.


Структура ошибки Yup и её трансформация

Yup возвращает ошибки в виде экземпляра ValidationError, который может содержать:

  • path — путь до поля (например user.email)
  • message — текст ошибки
  • inner — массив вложенных ошибок (при abortEarly: false)

Пример:

import * as yup from "yup";

const schema = yup.object({
  user: yup.object({
    email: yup.string().email().required(),
    age: yup.number().min(18),
  }),
});

При валидации с несколькими ошибками inner может выглядеть так:

[
  { path: "user.email", message: "Invalid email" },
  { path: "user.age", message: "Must be at least 18" }
]

Преобразование YupResolver

YupResolver выполняет следующие шаги:

  1. Выполняет schema.validate(data, { abortEarly: false })
  2. Ловит ValidationError
  3. Извлекает inner
  4. Строит объект ошибок

Результат:

{
  user: {
    email: { type: "validation", message: "Invalid email" },
    age: { type: "validation", message: "Must be at least 18" }
  }
}

Режим abortEarly и его влияние на ошибки

Флаг abortEarly критически влияет на программную обработку ошибок.

abortEarly: true

  • Возвращается только первая ошибка
  • inner может быть пустым
  • UI получает неполный список ошибок
schema.validate(data, { abortEarly: true });

Поведение YupResolver в этом режиме:

  • фиксируется только одно поле
  • остальные ошибки игнорируются
  • структура ошибок минимальна

abortEarly: false

  • собираются все ошибки
  • активируется массив inner
  • требуется агрегация
schema.validate(data, { abortEarly: false });

Это основной режим для YupResolver, так как он позволяет:

  • отображать все ошибки одновременно
  • корректно заполнять форму
  • выполнять массовую обработку ошибок

Нормализация путей ошибок

Пути (path) в Yup могут быть:

  • плоские: email
  • вложенные: user.email
  • массивы: items[0].name

YupResolver обязан преобразовать их в структуру, совместимую с React Hook Form.

Обработка вложенных объектов

{
  "user.email": "error"
}

преобразуется в:

{
  user: {
    email: { message: "error", type: "validation" }
  }
}

Обработка массивов

items[2].name

становится:

{
  items: [
    null,
    null,
    {
      name: { message: "error" }
    }
  ]
}

Ключевая сложность здесь — сохранение индексов без потери структуры массива.


Формирование объекта errors

YupResolver формирует FieldErrors через рекурсивный разбор path.

Алгоритм:

  1. Разбить путь по . и []
  2. Построить дерево объектов
  3. Вставить leaf-узел с ошибкой
  4. Сохранить метаинформацию

Пример внутреннего представления:

{
  _errors: ["global message"],
  user: {
    email: {
      _errors: ["Invalid email"]
    }
  }
}

Программная агрегация ошибок

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

Стратегии:

Перезапись ошибки

Последняя ошибка перезаписывает предыдущую:

email: "Must be valid"
email: "Required"

Итог:

email: "Required"

Сбор массива ошибок

email: ["Must be valid", "Required"]

Используется при criteriaMode: "all".


criteriaMode и расширенная обработка

В React Hook Form параметр criteriaMode влияет на структуру ошибок.

criteriaMode: “firstError”

  • фиксируется только первая ошибка
  • упрощённая структура

criteriaMode: “all”

  • собираются все ошибки по полю
  • структура:
email: {
  types: {
    required: "Email is required",
    pattern: "Invalid format"
  }
}

YupResolver должен преобразовать Yup-ошибки в этот формат через группировку по path.


Асинхронные ошибки и промисы

Yup поддерживает асинхронную валидацию:

yup.string().test("check-db", async (value) => {
  return await checkEmail(value);
});

YupResolver должен:

  • ожидать завершения Promise
  • корректно обрабатывать rejected state
  • преобразовывать thrown errors в ValidationError

Программная логика:

try {
  await schema.validate(data, { abortEarly: false });
} catch (err) {
  if (err instanceof ValidationError) {
    return mapErrors(err);
  }
}

Кастомные ошибки и расширение структуры

Yup позволяет генерировать кастомные ошибки:

throw new yup.ValidationError("Custom error", value, "field");

YupResolver должен:

  • сохранить message
  • корректно сопоставить path
  • не терять контекст value

Обработка edge cases

1. Пустой path

Иногда ошибки не имеют пути:

  • глобальные ошибки схемы
  • root-level validation

Решение:

errors._root = { message }

2. Неконсистентные вложенности

user.email.error
user[email]

Требуется унификация через нормализацию токенов.


3. Конфликт типов

Если поле ожидает number, но приходит строка:

  • Yup генерирует coercion error
  • YupResolver фиксирует type: "typeError"

Производительность обработки ошибок

При больших формах (100+ полей):

Узкие места:

  • рекурсивное построение дерева
  • многократное split path
  • обработка массива inner

Оптимизации:

  • кэширование path-разбиения
  • итеративное построение вместо рекурсии
  • использование Map для ускорения lookup

Стратегия объединения ошибок

При конфликте нескольких источников ошибок:

  • schema validation
  • manual validation
  • resolver validation

приоритет:

  1. schema errors (Yup)
  2. resolver transformation
  3. external overrides

Интеграция с React Hook Form

Финальный объект должен соответствовать контракту:

{
  values: TFieldValues,
  errors: FieldErrors<TFieldValues>
}

YupResolver обязан гарантировать:

  • отсутствие undefined в глубине errors
  • стабильную ссылочную структуру
  • неизменяемость входных данных

Типизация ошибок

В TypeScript контексте:

FieldError = {
  type: string;
  message?: string;
  ref?: Ref;
};

YupResolver приводит все ошибки к единому виду:

  • type: "validation"
  • message: string
  • опционально ref

Итоговая модель преобразования

Формально процесс можно описать как функцию:

YupValidationError → normalize(path, message, inner) → FieldErrors

С промежуточными этапами:

  • декомпозиция path
  • агрегация inner[]
  • построение дерева ошибок
  • нормализация типов
  • приведение к формату RHF

Поведение при частично валидных данных

Yup может вернуть валидные данные даже при наличии ошибок.

YupResolver разделяет:

  • values — частично валидные данные
  • errors — блокирующие ошибки

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

  • сохранять введённые данные
  • отображать ошибки без потери state
  • выполнять частичную обработку формы