Match для выбора схемы

alternatives().match позволяет задавать набор схем и выбирать одну из них в зависимости от структуры входных данных. Это механизм условной маршрутизации валидации, где данные не приводятся к единому формату заранее, а проверяются по нескольким альтернативным моделям.

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

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

Базовая структура alternatives().match

Общий вид конструкции:

const schema = Joi.alternatives().match('one')
  .try(
    Joi.string().min(3),
    Joi.number().integer(),
    Joi.object({
      id: Joi.number().required()
    })
  );

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

Режимы сопоставления

match(‘one’)

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

const schema = Joi.alternatives().match('one').try(
  Joi.string().pattern(/^[a-z]+$/),
  Joi.number().positive(),
  Joi.object({ type: Joi.string().valid('A') })
);

Поведение:

  • строка “abc” пройдёт первую схему
  • число 10 — вторую
  • объект — третью
  • значение, подходящее под две схемы одновременно, вызовет ошибку

match(‘any’)

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

const schema = Joi.alternatives().match('any').try(
  Joi.string().min(5),
  Joi.string().pattern(/^\d+$/)
);

Значение “123” пройдёт вторую схему, даже если не подходит под первую.

match(‘all’)

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

const schema = Joi.alternatives().match('all').try(
  Joi.string().min(3),
  Joi.string().pattern(/[a-z]/)
);

Строка должна удовлетворять обеим проверкам одновременно.

Сопоставление по структуре данных

На практике alternatives().match применяется для различения форматов входных данных. Типичный сценарий — различие между примитивами и объектами.

const schema = Joi.alternatives().match('one').try(
  Joi.string(),
  Joi.object({
    value: Joi.string().required()
  })
);

В этом случае строка обрабатывается как отдельный вариант, а объект — как структурированный контейнер.

Условное ветвление через match

В сложных схемах match используется для эмуляции условных операторов. Это позволяет отказаться от вложенных when и построить линейную структуру правил.

const schema = Joi.alternatives().match('one').try(
  Joi.object({
    kind: Joi.valid('user'),
    name: Joi.string().required()
  }),
  Joi.object({
    kind: Joi.valid('admin'),
    permissions: Joi.array().items(Joi.string()).required()
  })
);

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

Приоритет и порядок схем

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

const schema = Joi.alternatives().match('one').try(
  Joi.string().min(0),
  Joi.string().max(10)
);

Любая строка длиной до 10 символов удовлетворяет обеим схемам, что делает результат неоднозначным.

Использование с вложенными объектами

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

const schema = Joi.alternatives().match('one').try(
  Joi.object({
    event: Joi.valid('login'),
    userId: Joi.number().required()
  }),
  Joi.object({
    event: Joi.valid('error'),
    message: Joi.string().required(),
    code: Joi.number().required()
  })
);

Такой подход позволяет описывать несколько контрактов в одной точке схемы.

Комбинация с when

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

const schema = Joi.alternatives().match('one').try(
  Joi.object({
    type: Joi.valid('text'),
    payload: Joi.string()
  }),
  Joi.object({
    type: Joi.valid('binary'),
    payload: Joi.binary()
  })
);

Здесь match заменяет сложную цепочку условий.

Ошибки сопоставления

Типичные причины ошибок:

  • отсутствие совпадающей схемы
  • пересечение нескольких схем при режиме one
  • слишком общие правила (например, Joi.any() в одной из альтернатив)
  • конфликт типов (строка и объект одновременно покрываются разными схемами)
const schema = Joi.alternatives().match('one').try(
  Joi.any(),
  Joi.string()
);

Такая конструкция почти всегда приводит к неоднозначности.

Практика проектирования схем с match

При проектировании важно:

  • избегать перекрывающихся схем
  • использовать дискриминаторные поля (type, kind, event)
  • минимизировать использование Joi.any() внутри альтернатив
  • обеспечивать чёткие границы между форматами данных

Структурирование схем через match особенно эффективно в API, где один эндпоинт возвращает разные типы объектов в зависимости от состояния системы.

Поведение при преобразованиях

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

const schema = Joi.alternatives().match('one').try(
  Joi.number(),
  Joi.string()
);

Без отключения преобразований строка “123” может попасть в числовую ветку.

Масштабирование альтернатив

При увеличении количества схем match становится инструментом композиции. Вместо глубокой вложенности условий формируется плоский список правил.

const schema = Joi.alternatives().match('one').try(
  Joi.object({ event: Joi.valid('create'), data: Joi.object() }),
  Joi.object({ event: Joi.valid('update'), data: Joi.object() }),
  Joi.object({ event: Joi.valid('delete'), id: Joi.number() })
);

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