Allow и valid

Метод allow() в Joi расширяет допустимое множество значений для схемы валидации. Его ключевая задача — добавить «исключения» к уже существующим ограничениям, позволяя пройти валидацию значениям, которые иначе были бы отвергнуты базовой схемой.

Базовый принцип работы

В Joi любая схема изначально задаёт набор правил. Например:

const schema = Joi.string().min(3);

Такое правило допускает только строки длиной от 3 символов и выше. Попытка передать "" (пустую строку) приведёт к ошибке.

Использование allow() изменяет поведение:

const schema = Joi.string().min(3).allow('');

Теперь пустая строка становится допустимой, несмотря на то, что она нарушает правило min(3).

Важно понимать ключевую особенность: allow() не убирает существующие ограничения, а добавляет дополнительные разрешённые значения.

Добавление нескольких допустимых значений

Метод принимает несколько аргументов:

const schema = Joi.number().min(10).allow(null, 0);

В этом случае допустимыми становятся:

  • числа ≥ 10
  • null
  • 0

Даже если 0 не проходит проверку min(10), оно всё равно допускается.

Поведение с несовместимыми типами

allow() способен «пробить» типовые ограничения:

const schema = Joi.number().allow('unknown');

Хотя схема ожидает число, строка 'unknown' также становится допустимой.

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

Отличие от default()

allow() не подменяет значение и не задаёт значение по умолчанию. Следующий пример:

const schema = Joi.string().allow(null);

означает, что null допустим, но не заменяется на строку и не трансформируется.


valid()

Метод valid() задаёт строгое перечисление допустимых значений. В отличие от allow(), он не расширяет существующие правила, а заменяет их ограниченным набором.

Базовое использование

const schema = Joi.string().valid('admin', 'user', 'guest');

Теперь допустимы только три строки:

  • admin
  • user
  • guest

Любое другое значение вызовет ошибку валидации.

Поведение как “enum”

valid() фактически выполняет роль перечисления (enum):

const schema = Joi.number().valid(1, 2, 3);

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

Строгое ограничение набора

Ключевая особенность valid() — полное перекрытие других правил:

const schema = Joi.number().min(10).valid(5, 20);

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

  • правило min(10) фактически перестаёт влиять на значение 5
  • допустимыми становятся только 5 и 20

То есть valid() имеет приоритет над другими ограничениями в контексте допустимых значений.

Алиас only

В некоторых версиях Joi присутствует алиас:

Joi.string().only()

Он эквивалентен:

Joi.string().valid(...)

Ключевые различия allow() и valid()

Семантика

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

Поведение с правилами

Joi.number().min(10).allow(5)

Значение 5 проходит, несмотря на min(10).

Joi.number().min(10).valid(5)

Значение 5 также проходит, но только потому, что входит в список допустимых значений. Любое другое число вне списка будет отклонено, даже если оно соответствует min(10).

Расширение vs ограничение

  • allow() расширяет пространство допустимых значений
  • valid() сужает пространство допустимых значений до перечисления

Поведение с null и undefined

allow(null)

Joi.string().allow(null)

Разрешает null, даже если строка не допускает null по умолчанию.

valid(null)

Joi.string().valid(null)

Означает, что единственным допустимым значением может быть только null.

Если добавить строку:

Joi.string().valid(null, 'text')

то допустимыми становятся только null и 'text'.


Взаимодействие allow() и valid() в одной схеме

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

const schema = Joi.string().valid('a', 'b').allow('');

Логика обработки:

  • valid('a', 'b') ограничивает значения до a и b
  • allow('') добавляет пустую строку как дополнительное допустимое значение

Итоговый набор допустимых значений: a, b, ''.


Поведение с массивами и объектами

Массивы

Joi.array().items(Joi.number().valid(1, 2)).allow(null)

Допустимы:

  • массивы с числами 1 или 2
  • null

Объекты

const schema = Joi.object({
  role: Joi.string().valid('admin', 'user').allow('guest')
});

Здесь:

  • admin и user допустимы по valid
  • guest добавлен через allow

Влияние на кастомные сообщения ошибок

valid()

При нарушении valid() обычно возвращается ошибка:

"value" must be one of [a, b, c]

allow()

allow() не вызывает ошибку для добавленных значений, поэтому сообщения об ошибке относятся только к базовым ограничениям схемы.


Особенности применения в реальных схемах

Обработка legacy-значений

Joi.string().valid('active', 'inactive').allow('unknown')

Используется при миграции систем, где старые данные не соответствуют новой модели.

Совместимость с внешними API

Joi.number().min(0).allow(-1)

Часто -1 используется как служебное значение “не задано”.

Гибридные схемы

Joi.alternatives().try(
  Joi.string().valid('auto', 'manual'),
  Joi.number().allow(0)
)

Позволяет комбинировать строгие и расширенные правила в одной структуре.


Типичные ошибки при использовании

Ошибка: ожидание ограничения через allow()

Joi.string().allow('admin')

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

Ошибка: неправильное понимание valid()

Joi.number().valid(10).min(100)

Несмотря на min(100), значение 10 всё равно будет допустимо, потому что valid() переопределяет допустимые значения.

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

Использование allow() и valid() без чёткого понимания приоритетов может привести к неожиданным результатам, когда схема допускает больше значений, чем предполагается.


Приоритеты и логика разрешения значений

Внутренняя логика Joi при обработке этих методов строится так:

  • valid() формирует набор разрешённых значений
  • allow() добавляет дополнительные исключения
  • остальные правила (min, max, pattern и т.д.) применяются только если значение не попало в список разрешённых через valid() или allow()

Таким образом:

  • valid() задаёт жёсткий фильтр
  • allow() расширяет набор допустимых значений поверх фильтрации

Использование в композиции схем

const base = Joi.string().min(3);
const extended = base.allow('').valid('admin', 'user', '');

Здесь формируется гибрид:

  • минимальная длина 3 символа
  • допустимые роли admin, user
  • дополнительно разрешена пустая строка

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