Переход с версии на версию

Переход между версиями библиотеки валидации Joi требует внимательного анализа изменений в API, поведении схем и структуре ошибок. Несмотря на стремление к обратной совместимости, крупные релизы часто включают изменения, влияющие на существующие схемы валидации, обработку ошибок и интеграцию с TypeScript.


Общий подход к миграции

Миграция между версиями Joi строится вокруг последовательной адаптации схем и проверки поведения валидаторов в тестовой среде.

Ключевые этапы перехода:

  • фиксация текущей версии и поведения схем;
  • обновление версии пакета;
  • выявление ошибок выполнения и валидации;
  • корректировка схем под новые правила;
  • обновление обработки ошибок;
  • проверка строгих режимов и опций конфигурации.

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


Изменения структуры API

В разных версиях Joi происходили изменения в организации методов и поведении цепочек.

Устаревание старых методов

В более ранних версиях активно использовались методы:

  • any().required() в сочетании с неявной типизацией;
  • optional() как явное указание необязательности;
  • strip() и raw() с изменённой семантикой в новых версиях.

В современных версиях поведение стало более строгим, и некоторые конструкции требуют явного указания допустимости undefined и null.


Изменения валидации required, optional и allow

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

Ранее:

Joi.string().required()

или

Joi.string().optional()

В новых версиях:

  • required() сохраняет смысл обязательного поля;
  • optional() становится менее значимым при наличии глобальных настроек presence;
  • allow(null) и allow(undefined) требуют явного указания в сложных схемах.

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

Joi.object().options({ presence: 'required' })

где поведение всех полей может быть переопределено.


Изменения обработки ошибок

Структура ошибок в Joi претерпевала изменения, особенно в части детализации и формата details.

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

error.details[0].message
error.details[0].path

Новый формат:

  • расширенная структура context;
  • более точная типизация type;
  • изменение текстов сообщений при использовании встроенных языков;
  • различие между code и type в некоторых версиях.

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

if (error?.details?.length) {
  const type = error.details[0].type;
}

Изменения в кастомных сообщениях

Механизм кастомизации сообщений валидации изменился от строковых шаблонов к объектной структуре.

Ранее:

Joi.string().required().error(new Error('Ошибка'))

Позднее:

Joi.string().messages({
  'string.empty': 'Поле не может быть пустым',
  'any.required': 'Поле обязательно'
})

Изменение затрагивает:

  • ключи ошибок (string.base, any.only и др.);
  • локализацию сообщений;
  • порядок применения правил.

Изменения в работе с типами (TypeScript)

В современных версиях Joi значительно улучшена поддержка TypeScript, однако это приводит к несовместимости с ранними объявлениями типов.

Основные изменения:

  • усиление строгой типизации схем;
  • изменение вывода типов из object() схем;
  • различие между Schema и конкретными типами (StringSchema, NumberSchema);
  • более строгая проверка unknown полей.

Пример типизации:

const schema = Joi.object({
  id: Joi.number().required(),
  name: Joi.string().required()
});

В новых версиях вывод типа становится более строгим и требует явного указания generics в сложных структурах.


Изменения поведения unknown() и strict()

Режимы валидации стали более предсказуемыми, но менее «мягкими».

unknown()

  • ранее допускал неописанные поля без строгого контроля;
  • в новых версиях требует явного разрешения при вложенных объектах.

strict()

  • усиливает типовую проверку;
  • отключает автоматические преобразования (например, строка → число);
  • влияет на поведение convert.

Изменения преобразований типов

Joi традиционно выполняет автоматическое приведение типов:

  • строка "123" → число 123;
  • строка "true" → boolean true.

В новых версиях:

  • поведение зависит от convert: true|false;
  • строгие режимы могут полностью отключать преобразование;
  • требуется явное управление через .custom() или .alter().

Изменения в работе с альтернативами (alternatives)

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

Ранее:

Joi.alternatives().try(Joi.string(), Joi.number())

В новых версиях:

  • требуется более строгая проверка соответствия;
  • порядок схем влияет на результат чаще, чем раньше;
  • добавлены дополнительные методы контроля:
Joi.alternatives().conditional(...)

Изменения в валидации массивов

Работа с массивами стала более строгой в части:

  • проверки элементов;
  • правил items();
  • поведения sparse.

Пример:

Joi.array().items(Joi.string().required())

В новых версиях:

  • пустые значения обрабатываются строже;
  • undefined внутри массива требует явного разрешения;
  • min и max применяются до преобразований.

Изменения в validate() и результатах

Функция валидации изменила структуру возвращаемого объекта.

Ранее:

const { error, value } = schema.validate(data);

В новых версиях:

  • добавлены дополнительные поля результата;
  • warning используется в некоторых режимах;
  • изменён формат value при использовании stripUnknown.

Стратегии адаптации схем

Переход между версиями требует системного подхода к обновлению схем:

  • замена неявных правил на явные;
  • минимизация зависимости от автоматического преобразования типов;
  • проверка всех messages;
  • тестирование edge-case значений (null, undefined, пустые строки);
  • фиксация поведения через snapshot-тесты.

Проблемные зоны при обновлении

Наиболее частые источники несовместимости:

  • неявные преобразования типов;
  • отсутствие явного required или optional;
  • устаревшие ключи ошибок;
  • различие поведения allow(null);
  • использование кастомных валидаторов без учёта нового контекста;
  • различия в работе alternatives.

Совместимость версий и зависимостей

Joi тесно связан с экосистемой Node.js и часто требует согласования:

  • версии Node.js (особенно для последних релизов);
  • совместимости с @hapi пакетами;
  • корректной версии @types/joi (или встроенных типов);
  • отсутствия конфликтов с другими валидаторами.

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


Пример миграции схемы

До обновления:

const schema = Joi.object({
  id: Joi.string().required(),
  age: Joi.number().optional().default(18),
  tags: Joi.array().items(Joi.string()).allow(null)
});

После обновления:

const schema = Joi.object({
  id: Joi.string().required(),
  age: Joi.number().default(18),
  tags: Joi.array().items(Joi.string()).allow(null).required(false)
}).options({ convert: true });

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