Обратная совместимость

Обратная совместимость в Yup играет ключевую роль при эволюции схем в долгоживущих JavaScript-проектах, где валидация данных тесно связана с API, формами и бизнес-логикой. Любое изменение поведения библиотеки или схемы способно затронуть критические участки приложения, поэтому механизмы сохранения предсказуемости поведения между версиями становятся неотъемлемой частью архитектуры.

При работе с валидацией данных важно различать несколько типов изменений:

  • Ломающие изменения (breaking changes) — изменение поведения схем, при котором ранее валидные данные становятся невалидными или наоборот.
  • Обратно совместимые изменения — расширение схемы без изменения поведения существующих правил.
  • Поведенческие изменения — изменения в порядке обработки, дефолтах или преобразованиях данных.

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

Структура схем и влияние на совместимость

Схемы валидации в Yup строятся как композиция цепочек методов (string(), number(), object() и т.д.), где каждый последующий вызов модифицирует поведение предыдущего.

Ключевой особенностью является то, что порядок и комбинация методов влияет на итоговую логику:

  • required() добавляет обязательность поля
  • nullable() изменяет допустимость null
  • default() задаёт значение при отсутствии данных
  • transform() меняет входное значение до валидации

Любое изменение в логике этих методов между версиями может привести к нарушению обратной совместимости. Например, если раньше undefined автоматически приводился к значению по умолчанию, а в новой версии — нет, поведение форм и API меняется без изменения кода схем.

Версионирование и стратегия эволюции API

Поддержание совместимости в Yup обычно связано с семантическим версионированием. При переходе между мажорными версиями могут происходить следующие изменения:

  • изменение поведения cast() и validate()
  • переработка логики strict() режима
  • изменение работы трансформаций (transform)
  • уточнение типизации в TypeScript-интеграции

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

Особенно критичным является момент, когда библиотека меняет дефолтное поведение валидации. Например, если ранее невалидные строки автоматически приводились к числам через неявное преобразование, а затем это поведение стало требовать явного transform, это приводит к скрытым регрессиям.

Объектные схемы и глубокая совместимость

Наиболее сложные сценарии возникают при работе с object() схемами, где структура вложена:

  • вложенные объекты
  • массивы объектов
  • условная валидация через when()

В таких случаях изменение даже одного поля внутри вложенной схемы может сломать внешнюю логику. Особенно важно поведение метода shape().

Поведение shape() и расширение схем

Метод shape() в Yup позволяет описывать структуру объекта. Однако при изменении схемы возникает вопрос:

  • происходит ли полная замена структуры
  • или происходит её частичное слияние

Если поведение между версиями меняется (например, ранее новые поля добавлялись поверх старых, а теперь происходит полная перезапись), это приводит к несовместимости схем без изменения кода.

Условная валидация и нестабильность поведения

Метод when() является одним из наиболее чувствительных к изменениям в библиотеке. Он позволяет изменять схему в зависимости от других значений:

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

Если логика разрешения условий меняется между версиями Yup, поведение форм становится непредсказуемым. Например, порядок вычисления условий или приоритет зависимостей может привести к различным результатам валидации при одинаковых данных.

Работа с дефолтами и неявные преобразования

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

В старых версиях поведение могло быть следующим:

  • undefined автоматически заменяется на default()
  • пустая строка интерпретируется как отсутствие значения

В более строгих версиях:

  • default() применяется только при полном отсутствии ключа
  • пустые строки требуют явной трансформации

Такие изменения в Yup приводят к тому, что данные, ранее проходившие валидацию, начинают её не проходить без изменения схемы.

TypeScript и обратная совместимость типов

С появлением строгой типизации в Yup значительное внимание стало уделяться соответствию runtime-валидации и compile-time типов.

Проблемные зоны:

  • несовпадение InferType с реальной логикой схемы
  • изменение типов nullable и optional
  • различие между stripUnknown и фактической структурой объекта

Если библиотека изменяет внутренние типы без изменения runtime поведения или наоборот, возникает рассинхронизация между типами и валидацией.

Стратегии миграции между версиями

При переходе на новую версию Yup обычно используются следующие подходы:

1. Явная фиксация поведения схем

Схемы переписываются таким образом, чтобы исключить зависимость от дефолтного поведения:

  • явные default()
  • явные transform()
  • минимизация неявных преобразований

2. Изоляция критичных схем

Схемы, используемые в API и формах, выносятся в отдельные модули, что позволяет тестировать их независимо от версии библиотеки.

3. Постепенная миграция

Вместо массового обновления всей системы схемы обновляются поэтапно:

  • сначала некритичные формы
  • затем API-валидация
  • затем сложные вложенные структуры

4. Тестирование контрактов

Автоматизированные тесты проверяют:

  • идентичность результата validate()
  • стабильность cast()
  • отсутствие регрессий в transform()

Совместимость кастомных тестов

Метод test() в Yup позволяет добавлять пользовательские правила. Однако именно он чаще всего становится источником проблем совместимости:

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

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

Поведение ошибок и обратная совместимость UX

Отдельное внимание уделяется структуре ошибок:

  • формат ValidationError
  • вложенные ошибки в объектах
  • путь (path) к ошибке
  • агрегирование ошибок массива

Изменение структуры ошибок между версиями Yup напрямую влияет на пользовательский интерфейс, поскольку многие UI-библиотеки используют эти данные для отображения сообщений.

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

Ленивые схемы и динамическая совместимость

Метод lazy() позволяет создавать схемы на основе входных данных. Это делает поведение особенно чувствительным:

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

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

Итоговая устойчивость системы схем

Обратная совместимость в Yup не ограничивается только сохранением API методов. Она включает:

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

Любое отклонение в этих областях превращает миграцию между версиями в задачу переписывания логики, а не простого обновления зависимостей.