Дебаггинг схем валидации

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

Ключевые поля ValidationError:

  • message — текст ошибки, часто агрегированный
  • path — путь к полю, где произошла ошибка (user.email, items[2].price)
  • errors — массив всех сообщений (если ошибок несколько)
  • inner — массив вложенных ошибок (особенно полезен для массивов и объектов)

Именно inner становится основным инструментом анализа сложных структур, где одна схема включает множество вложенных проверок.

Типичная ошибка при отсутствии анализа inner — восприятие только первой проблемы, хотя фактическая причина может находиться глубже в структуре.


Поведение abortEarly и влияние на диагностику

По умолчанию Yup останавливает валидацию при первой найденной ошибке (abortEarly: true). Это значительно усложняет отладку, так как скрывает остальные проблемы.

Изменение поведения:

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

или

Yup.object().shape({...}).validate(data, { abortEarly: false })

При отключении раннего прерывания:

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

Однако увеличивается объем информации, которую нужно структурировать вручную.


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

Поле path является ключевым инструментом точечного поиска проблем.

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

const schema = Yup.object({
  user: Yup.object({
    email: Yup.string().email(),
  }),
});

При ошибке email:

path: "user.email"

Для массивов:

items[3].price

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

  • быстро находить проблемные поля
  • строить визуальные дебаг-инструменты
  • связывать ошибки с UI-формами

Частая проблема — игнорирование path и попытка анализировать только message, что приводит к потере контекста.


Разбор вложенных схем через inner

В сложных схемах (особенно array().of(object())) основная информация скрывается в inner.

Структура:

inner: [
  { path: "items[0].name", message: "Required" },
  { path: "items[2].price", message: "Must be positive" }
]

Методика анализа:

  1. Группировка ошибок по path
  2. Сопоставление с исходной схемой
  3. Выделение повторяющихся паттернов

Особенно важно при динамических массивах, где индексы элементов меняются.


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

Yup поддерживает два режима:

  • validate() — асинхронный (Promise)
  • validateSync() — синхронный

Отладка различается:

validateSync

  • моментальная ошибка
  • проще трассировать стек
  • подходит для unit-тестов

validate

  • ошибки приходят в Promise rejection
  • сложнее отслеживать порядок выполнения

Типичная ошибка — смешивание режимов в одном потоке обработки данных, что приводит к “потерянным” исключениям.


Логирование промежуточных значений схемы

При сложной композиции схем полезно анализировать не только данные, но и саму структуру валидации.

Подходы:

Вывод схемы через describe()

console.log(schema.describe());

Результат содержит:

  • типы полей
  • правила валидации
  • вложенную структуру

Это помогает выявить:

  • неверные типы (number vs string)
  • отсутствующие поля
  • неправильно собранные shape

Отладка трансформаций (transform)

Метод transform() часто становится скрытым источником ошибок.

Yup.string().transform(value => value.trim())

Проблемы:

  • неожиданные undefined
  • потеря исходного значения
  • изменение типа данных до валидации

При дебаге важно временно отключать трансформации, чтобы определить, на каком этапе возникает искажение данных.


Условная логика when и скрытые ветки схем

Метод when() создаёт динамическую схему, поведение которой зависит от входных данных.

Пример:

Yup.string().when("isAdmin", {
  is: true,
  then: schema => schema.required(),
})

Проблема:

  • фактическая схема зависит от runtime-значения
  • статический анализ через describe() не отражает реальное состояние

При отладке важно фиксировать входные условия, при которых формируется схема.


Проверка частичных схем через reach

Метод reach() позволяет изолировать конкретную ветку схемы:

Yup.reach(schema, "user.profile.email")

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

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

Это особенно эффективно при глубокой вложенности объектов.


Типичные ошибки построения схем

1. Несовпадение типов

Yup.number().required()

при передаче строки "123" может не работать без coercion.

Решение:

  • использование transform
  • или typeError

2. Отсутствие required

Yup.string().email()

Поле может проходить как undefined, если не указан required().


3. Неправильная работа nullable

Yup.string().nullable()

Без дополнительной проверки required() допускает null, но не всегда это ожидаемое поведение.


4. Игнорирование stripUnknown

Yup.object().stripUnknown(true)

Без этого неизвестные поля могут попадать в результат, и это создаёт ложное ощущение корректной валидации.


Изоляция проблемных участков схемы

При сложных объектах эффективной стратегией является пошаговое упрощение:

  1. Удаление вложенных объектов
  2. Проверка базовых типов
  3. Постепенное добавление веток
  4. Фиксация точки появления ошибки

Этот метод особенно полезен при схемах с множеством when, array.of, lazy.


Ошибки в массивах и nested validation

Массивы — наиболее проблемная зона в Yup.

Yup.array().of(
  Yup.object({
    name: Yup.string().required(),
  })
)

Основные проблемы:

  • несоответствие индексов в inner
  • отсутствие привязки ошибки к конкретному элементу UI
  • массовые ошибки без детализации при abortEarly: false

Для диагностики важно анализировать:

  • inner[].path
  • индекс элемента массива
  • структуру исходных данных

Тестирование схем как метод отладки

Использование unit-тестов позволяет выявлять ошибки схем до интеграции:

  • проверка валидных кейсов
  • проверка граничных значений
  • проверка некорректных типов

Типичный паттерн:

expect(schema.isValid(data)).toBe(false)

или

await expect(schema.validate(data)).rejects.toThrow()

Работа с сообщениями ошибок

Стандартные сообщения Yup часто недостаточно информативны в сложных системах.

Подходы улучшения:

  • кастомизация через message
  • использование функции в test()
  • стандартизация формата ошибок (код + текст + path)

Пример:

Yup.string().test("len", "INVALID_LENGTH", value => value.length > 3)

Это упрощает интеграцию с логированием и UI-валидацией.


Диагностика асинхронных тестов (test)

Функция test() может быть асинхронной:

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

Проблемы:

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

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


Сравнение validate vs isValid

  • validate() — возвращает данные или ошибку
  • isValid() — boolean без деталей

При дебаге предпочтителен validate(), так как он предоставляет:

  • path
  • inner
  • структуру ошибки

isValid() используется только для быстрых проверок без диагностики.


Стратегии системного дебага схем

Эффективная диагностика строится на сочетании:

  • анализа ValidationError.inner
  • изоляции через reach
  • временного упрощения схем
  • логирования describe()
  • отключения abortEarly
  • тестирования отдельных узлов

Главная сложность Yup-схем — их декларативность: ошибка часто не находится в месте, где она проявляется, а возникает в трансформациях, условиях или вложенных объектах.