Conditional на основе других полей

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

Основной инструмент условной логики в Joi — метод .when(). Он применяется к полю схемы и позволяет задавать поведение в зависимости от другого поля.

Синтаксис опирается на указание ссылки на зависимое поле и набор условий:

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

  permissions: Joi.array().when('role', {
    is: 'admin',
    then: Joi.required(),
    otherwise: Joi.forbidden()
  })
});

В этом примере поле permissions становится обязательным только при значении role = admin. Во всех остальных случаях оно запрещено.

Ключевые элементы конструкции:

  • is — значение или условие сравнения
  • then — схема при выполнении условия
  • otherwise — схема при невыполнении условия

Использование ссылок через Joi.ref

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

const schema = Joi.object({
  password: Joi.string().min(8),

  confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});

Здесь confirmPassword должен совпадать с password. Это типичный пример межполевой зависимости без явного when.

Условие на основе нескольких значений

when поддерживает сложные конструкции с альтернативными условиями через массив:

const schema = Joi.object({
  type: Joi.string().valid('email', 'phone'),

  contact: Joi.string().when('type', [
    {
      is: 'email',
      then: Joi.string().email().required()
    },
    {
      is: 'phone',
      then: Joi.string().pattern(/^\d+$/).required()
    }
  ])
});

Такой подход позволяет описывать ветвление логики, аналогичное switch-case на уровне схемы.

Вложенные зависимости

Joi допускает каскадные условия, когда одно поле зависит от другого, а тот — от третьего:

const schema = Joi.object({
  accountType: Joi.string().valid('personal', 'business'),

  companyName: Joi.string().when('accountType', {
    is: 'business',
    then: Joi.required().when('country', {
      is: 'US',
      then: Joi.string().min(2),
      otherwise: Joi.string().min(3)
    }),
    otherwise: Joi.forbidden()
  }),

  country: Joi.string()
});

Здесь поведение поля изменяется не только от одного параметра, но и от комбинации нескольких факторов.

Использование alternatives().conditional

Альтернативный способ выражения условий реализуется через Joi.alternatives(), который позволяет строить набор возможных схем:

const schema = Joi.object({
  paymentMethod: Joi.string().valid('card', 'paypal'),

  paymentDetails: Joi.alternatives().conditional('paymentMethod', {
    switch: [
      {
        is: 'card',
        then: Joi.object({
          cardNumber: Joi.string().creditCard().required(),
          cvv: Joi.string().length(3).required()
        })
      },
      {
        is: 'paypal',
        then: Joi.object({
          email: Joi.string().email().required()
        })
      }
    ]
  })
});

В этом случае структура объекта полностью меняется в зависимости от значения paymentMethod.

Условная обязательность через required()

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

const schema = Joi.object({
  hasDiscount: Joi.boolean(),

  discountCode: Joi.string().when('hasDiscount', {
    is: true,
    then: Joi.required(),
    otherwise: Joi.optional()
  })
});

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

Зависимость от отсутствия значения

Условие может срабатывать не только по значению, но и по его отсутствию:

const schema = Joi.object({
  referralCode: Joi.string(),

  signupBonus: Joi.number().when('referralCode', {
    is: Joi.exist(),
    then: Joi.valid(0),
    otherwise: Joi.number().min(10)
  })
});

Здесь логика зависит от того, присутствует ли поле в объекте.

Использование контекста через .context()

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

const schema = Joi.object({
  age: Joi.number().when(Joi.ref('$isAdmin'), {
    is: true,
    then: Joi.optional(),
    otherwise: Joi.required()
  })
});

Контекст передаётся при валидации:

schema.validate(data, { context: { isAdmin: true } });

Валидация массивов с условиями

Условные конструкции применимы и к массивам:

const schema = Joi.object({
  items: Joi.array().items(Joi.object({
    type: Joi.string().valid('digital', 'physical'),

    downloadLink: Joi.string().when('type', {
      is: 'digital',
      then: Joi.required(),
      otherwise: Joi.forbidden()
    })
  }))
});

Каждый элемент массива может иметь собственную условную логику.

Логические ограничения между полями

Joi позволяет строить взаимоисключающие или зависимые поля:

const schema = Joi.object({
  startDate: Joi.date(),
  endDate: Joi.date().greater(Joi.ref('startDate'))
});

Здесь второе поле зависит от значения первого и должно быть строго больше.

Другой пример взаимного исключения:

const schema = Joi.object({
  email: Joi.string(),
  phone: Joi.string()
}).or('email', 'phone');

Это задаёт правило: должно быть указано хотя бы одно из полей.

Комбинирование условий

Сложные схемы часто требуют объединения нескольких механизмов:

const schema = Joi.object({
  role: Joi.string().valid('admin', 'editor'),

  accessLevel: Joi.number().when('role', {
    switch: [
      {
        is: 'admin',
        then: Joi.number().min(10).required()
      },
      {
        is: 'editor',
        then: Joi.number().min(5).required()
      }
    ]
  })
});

Такой подход делает схему масштабируемой при росте количества условий.

Типовые ошибки при построении условий

При работе с условной логикой часто возникают следующие проблемы:

  • использование несовместимых типов в is
  • циклические зависимости между полями
  • отсутствие otherwise, приводящее к неопределённому поведению
  • чрезмерная вложенность when, усложняющая поддержку схем

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

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