Миграция со старых версий

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


Изменение модели валидации и поведения по умолчанию

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

abortEarly

В старых версиях часто использовалась логика раннего завершения проверки:

  • при первой ошибке валидация прекращалась;
  • возвращалась только одна ошибка.

В более новых версиях поведение становится более гибким, но при миграции важно явно фиксировать нужный режим:

Joi.object({
  email: Joi.string().email(),
  password: Joi.string().min(8)
}).validate(data, { abortEarly: false });

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


allowUnknown и stripUnknown

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

Ключевые изменения:

  • allowUnknown становится более явно управляемым;
  • stripUnknown используется чаще как безопасная практика.

Пример обновлённого поведения:

Joi.object({
  username: Joi.string().required()
}).validate(data, {
  allowUnknown: false,
  stripUnknown: true
});

В процессе миграции важно учитывать, что ранее «лишние» поля могли проходить незаметно.


Переход от callback-валидации к Promise API

Старые версии активно использовали callback-подход:

schema.validate(data, (err, value) => {
  // обработка
});

В более новых версиях предпочтение отдаётся Promise-ориентированному API:

await schema.validateAsync(data);

При миграции требуется:

  • заменить callback-логику на async/await;
  • пересмотреть обработку ошибок через try/catch;
  • унифицировать слой валидации.

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

Детализация ошибок

В старых версиях структура ошибок была менее стандартизированной. Новые версии усиливают консистентность:

  • details становится основным источником информации;
  • сообщения ошибок нормализуются;
  • добавляются стабильные коды причин.

Пример обработки:

try {
  await schema.validateAsync(data);
} catch (err) {
  err.details.forEach(d => {
    console.log(d.message);
  });
}

Кастомизация сообщений

Ранее часто использовались простые строки. В новых версиях расширяется система messages:

Joi.string().min(8).messages({
  'string.min': 'Строка слишком короткая'
});

При миграции важно заменить устаревшие методы кастомизации на новую систему ключей сообщений.


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

В процессе эволюции Joi ряд методов был изменён или признан устаревшим.

required / optional

Ранее часто использовались неявные комбинации:

Joi.string().required()

Хотя базовая логика сохраняется, в новых версиях усиливается явность схем:

  • обязательность должна быть явно указана;
  • поведение nullable требует отдельной настройки.

any.allow()

Методы вроде allow() в старых версиях использовались более свободно, включая неожиданные типы значений. В новых версиях:

  • повышается строгость типов;
  • расширенные значения требуют явного перечисления.

Изменения в цепочках валидации

when и альтернативы

Условная логика валидации претерпела изменения.

Старые конструкции:

Joi.string().when('role', {
  is: 'admin',
  then: Joi.required()
});

В новых версиях усиливается читаемость и строгая типизация условий.

Особенности миграции:

  • проверка условий становится более предсказуемой;
  • вложенные when требуют пересмотра;
  • альтернативы (alternatives()) используются чаще.

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

глубина валидации

Ранее вложенные объекты могли частично игнорировать строгие правила. Новые версии усиливают:

  • рекурсивную проверку;
  • наследование правил;
  • явное управление глубиной через настройки.

модификация значений

Поведение default() и cast становится более детерминированным.

Joi.object({
  createdAt: Joi.date().default(Date.now)
});

При миграции важно учитывать:

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

Изменения в API композиции схем

alternatives()

Механизм альтернативных схем становится более строгим:

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

Пример:

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

В процессе миграции важно пересмотреть все места, где типы ранее определялись неявно.


Строгая типизация и преобразования

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

  • строки не всегда автоматически приводятся к числам;
  • даты требуют явной обработки;
  • boolean преобразования становятся более предсказуемыми.

Пример изменения поведения:

Joi.number().strict()

Использование strict() становится важным инструментом при миграции для выявления скрытых преобразований.


Работа с расширениями (extensions)

В старых версиях расширение схем было менее структурированным. В новых версиях:

  • расширения оформляются через более строгие API;
  • требуется явное определение базового типа;
  • усиливается контроль совместимости.

При миграции кастомных валидаторов необходимо:

  • проверить сигнатуры расширений;
  • обновить обработчики ошибок;
  • адаптировать контекст выполнения.

Совместимость и стратегический подход к миграции

При переходе между версиями Joi основная сложность заключается не в синтаксисе, а в изменении семантики.

Рекомендуемая стратегия:

  • инвентаризация всех схем;
  • проверка поведения ошибок;
  • фиксация текущего поведения через тесты;
  • поэтапное включение новых версий;
  • адаптация строгих режимов (strict, abortEarly, stripUnknown).

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

  • API validate vs validateAsync;
  • поведению вложенных схем;
  • кастомным сообщениям ошибок;
  • альтернативным схемам и условиям.

Типичные проблемы при обновлении

Непредсказуемые ошибки

Из-за изменения структуры details могут измениться форматы логирования.

Потеря неявного приведения типов

Код, зависящий от слабой типизации, начинает падать.

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

Значения по умолчанию могут применяться в другом порядке.

Изменение обработки unknown полей

Ранее допустимые данные начинают отклоняться или удаляться.


Практика адаптации больших проектов

В крупных кодовых базах миграция обычно включает:

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

Такая стратегия снижает риск регрессий при переходе между версиями.