Маппинг ошибок на поля формы

Валидация на основе схемы Yup формирует ошибки в стандартизированном виде, который затем интерпретируется резолверами, включая YupResolver. Центральной единицей является объект ValidationError, содержащий:

  • message — текст ошибки
  • path — путь до поля в форме
  • type — тип ошибки (например, required, min, matches)
  • inner — массив вложенных ошибок (при множественной валидации)

Ключевым свойством для маппинга является path. Именно он определяет, в какое поле формы будет записана ошибка.


Принцип преобразования ValidationError в структуру формы

Резолвер выполняет трансформацию:

Yup ValidationError → { fieldName: { message, type } }

или для вложенных структур:

user.address.city → errors.user.address.city

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


Механизм работы inner-ошибок

При отключённом abortEarly: true, Yup возвращает сразу все ошибки:

{
  inner: [
    { path: "email", message: "Email некорректен" },
    { path: "password", message: "Слишком короткий пароль" }
  ]
}

Резолвер проходит по массиву inner и агрегирует их в объект:

  • группировка по path
  • приоритет первой ошибки (или последней — зависит от конфигурации)
  • нормализация формата

Если inner пустой, используется корневой ValidationError.


Алгоритм маппинга path → errors

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

1. Разбор пути

path вида:

  • user.name
  • items[0].title
  • profile.address.city

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

["user", "name"]
["items", "0", "title"]
["profile", "address", "city"]

2. Построение дерева ошибок

Создаётся вложенный объект:

errors.user.name = { message, type }
errors.items[0].title = { message, type }

3. Нормализация индексов массивов

Индексы массивов приводятся к числовым ключам, чтобы корректно совпадать с field array структурами:

items.0.title → errors.items[0].title

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

Вложенные структуры требуют рекурсивного маппинга:

const schema = yup.object({
  user: yup.object({
    profile: yup.object({
      age: yup.number().required()
    })
  })
});

Ошибка:

user.profile.age

Результат:

errors.user.profile.age.message

Резолвер обязан сохранять полную вложенность без потери контекста.


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

Массивы являются наиболее сложным случаем, так как сочетают:

  • индексированную структуру
  • динамическое добавление/удаление элементов
  • частично заполненные данные

Пример:

items: [
  { title: "" },
  { title: "Valid" }
]

Ошибка Yup:

items[0].title: "Обязательное поле"

Маппинг:

errors.items[0].title

При удалении элементов важно сохранять согласованность индексов, иначе ошибки будут «съезжать» на другие элементы массива.


Приоритет ошибок и стратегия выбора сообщения

Если одно поле возвращает несколько ошибок, применяется стратегия:

  1. первая ошибка (abortEarly: true)
  2. первая ошибка в inner
  3. последняя ошибка (реже используемый вариант)

Резолвер может конфигурироваться:

  • strict mode — первая ошибка
  • aggregated mode — объединение сообщений
  • debug mode — сохранение всех типов ошибок

Типизация ошибок и расширение структуры

Стандартный формат:

{
  message: string,
  type: string
}

Расширенный формат:

{
  message: string,
  type: string,
  ref: any,
  types: object
}

types используется для сложных схем (например, min + required одновременно).


Обработка кастомных ошибок

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

yup.string().test("custom", function (value) {
  return value === "admin"
    ? true
    : this.createError({ message: "Недопустимое значение" });
});

Резолвер обязан:

  • извлечь message
  • корректно привязать path
  • сохранить type (если задан)

Проблемы неоднозначного path

Некоторые случаи приводят к отсутствию path:

  • ошибки на уровне схемы (this)
  • кастомные тесты без привязки к полю
  • глобальные ошибки объекта

Решение:

  • привязка к корневому ключу (root)
  • игнорирование в field-level errors
  • отдельное хранение formError

Нормализация структуры для UI-слоя

UI-слой ожидает формат:

{
  fieldName: {
    message: string,
    type: string
  }
}

Резолвер выполняет:

  • удаление служебных полей Yup
  • очистку undefined
  • фильтрацию null-path ошибок
  • стабилизацию структуры между рендерами

Кэширование и оптимизация маппинга

При больших формах (100+ полей) маппинг может быть дорогим. Используются техники:

  • мемоизация результатов схемы
  • повторное использование parsed paths
  • кеширование структуры ошибок по hash входных данных

Конфликты вложенных ошибок

При пересечении путей:

user.email
user.email.confirm

Возможны конфликты при агрегации. Резолвер обязан:

  • не перезаписывать родительские ошибки
  • сохранять точность до leaf-узла
  • избегать «подъёма» ошибок вверх по дереву

Согласование с динамическими схемами

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

yup.object({
  isCompany: yup.boolean(),
  companyName: yup.string().when("isCompany", {
    is: true,
    then: yup.string().required()
  })
});

Маппинг ошибок зависит от состояния схемы на момент резолва. Это означает:

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

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

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

Input: Yup ValidationError tree
↓
Flatten inner errors
↓
Normalize path strings
↓
Build nested object by segments
↓
Attach { message, type }
↓
Return form-compatible error map

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