Чувствительность к регистру

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

При использовании Joi.string() и последующих ограничений проверка выполняется строго, без приведения к единому регистру. Это означает, что значения "Admin" и "admin" считаются различными.

Такое поведение проявляется во всех конструкциях, где происходит сравнение строк:

  • перечисления допустимых значений (valid)
  • исключения (invalid)
  • строгие совпадения (equal)
  • регулярные выражения без флага игнорирования регистра

Пример логики:

  • ожидается "user"
  • вход "User" → ошибка валидации
  • вход "user" → проходит проверку

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

Регистрозависимость в перечислениях значений

Методы valid() и invalid() используют строгое сравнение строк. Без дополнительных опций значения считаются различными при любом отличии регистра.

Joi.string().valid('admin', 'user')

Допустимыми будут только точные совпадения:

  • "admin" → допустимо
  • "Admin" → недопустимо
  • "USER" → недопустимо

Для устранения этой проблемы используется метод insensitive().

Игнорирование регистра в valid и invalid

Метод insensitive() изменяет поведение сравнения строковых значений, делая его регистронезависимым.

Joi.string().valid('admin', 'user').insensitive()

Теперь допустимы варианты:

  • "admin"
  • "Admin"
  • "ADMIN"
  • "user"
  • "User"

При этом сравнение происходит по нормализованной форме, но исходное значение не изменяется автоматически.

Аналогично работает и invalid():

Joi.string().invalid('admin').insensitive()

Любая форма написания "admin" будет считаться недопустимой.

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

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

Joi.string().pattern(/abc/)

В этом случае:

  • "abc" → допустимо
  • "ABC" → недопустимо
  • "aBc" → недопустимо

Чтобы сделать проверку регистронезависимой, используется флаг i:

Joi.string().pattern(/abc/i)

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

  • "abc"
  • "ABC"
  • "AbC"

Важно учитывать, что использование регулярных выражений с флагом i полностью перекладывает контроль над регистром на саму регулярку, а Joi не выполняет дополнительной нормализации.

Ограничения и особенности insensitive()

Метод insensitive() работает только с механизмами сравнения значений, а не с произвольными преобразованиями строки.

Он применяется:

  • к valid()
  • к invalid()
  • к equal()

Он не влияет на:

  • pattern()
  • email()
  • uri()
  • пользовательские правила через custom()

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

Объектные ключи и регистр

При валидации объектов через Joi.object() ключи также чувствительны к регистру.

Joi.object({
  user: Joi.string()
})

Объект:

{
  User: "value"
}

не будет соответствовать схеме, так как ключ User отличается от user.

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

Влияние преобразований (coercion) на регистр

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

Joi.string().required()

Значение "TEXT" останется "TEXT", если не применяется явное преобразование:

  • trim() удаляет пробелы, но не меняет регистр
  • lowercase() приводит строку к нижнему регистру
  • uppercase() приводит строку к верхнему регистру

Эти методы позволяют управлять регистром явно:

Joi.string().lowercase()

Теперь любое значение будет приведено к нижнему регистру перед проверкой.

Комбинирование методов управления регистром

На практике часто комбинируются преобразования и проверки:

Joi.string()
  .lowercase()
  .valid('admin', 'user')

В этом случае:

  • вход "ADMIN" → преобразуется в "admin" → проходит проверку
  • вход "User" → преобразуется в "user" → проходит проверку

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

Различие между insensitive и lowercase

Механизмы выглядят схожими, но имеют принципиальное различие:

  • insensitive() не изменяет значение, только влияет на сравнение
  • lowercase() изменяет само значение до этапа проверки

Это влияет на итоговое значение после валидации:

  • с insensitive() вход остаётся в исходном регистре
  • с lowercase() результат всегда нормализован

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

Поведение в сложных схемах

При использовании вложенных схем поведение регистрозависимости сохраняется на каждом уровне отдельно. Например, внутри alternatives() или вложенных object() правила сравнения не наследуются автоматически, а применяются локально к каждому узлу схемы.

Это приводит к тому, что в одной структуре могут одновременно существовать поля:

  • строго регистрозависимые
  • частично нормализованные
  • полностью нечувствительные к регистру

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