When и условная валидация

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

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

const Joi = require('joi');

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

  accessLevel: Joi.when('role', {
    is: 'admin',
    then: Joi.number().min(5),
    otherwise: Joi.number().min(1)
  })
});

В данном примере поле accessLevel получает разные ограничения в зависимости от значения role. Если роль администратора, требования строже.

Структура when: is, then, otherwise

Основные компоненты условной валидации:

  • is — значение или схема, с которой выполняется сравнение
  • then — схема, применяемая при выполнении условия
  • otherwise — схема, применяемая в противном случае
password: Joi.string().min(8),

confirmPassword: Joi.when('password', {
  is: Joi.exist(),
  then: Joi.required(),
  otherwise: Joi.optional()
})

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

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

Joi.ref позволяет явно ссылаться на другие поля и использовать их значения в условиях.

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

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

Здесь значение repeatPassword должно совпадать с password.

Условная логика через типы и диапазоны

when поддерживает не только сравнение с конкретным значением, но и диапазоны или типы схем.

age: Joi.number(),

guardianConsent: Joi.boolean().when('age', {
  is: Joi.number().min(18),
  then: Joi.forbidden(),
  otherwise: Joi.required()
})

Поле становится запрещённым или обязательным в зависимости от возраста.

Условие по отсутствию или наличию поля

Можно проверять существование значения через Joi.exist() или Joi.not().

email: Joi.string().email(),

phone: Joi.when('email', {
  is: Joi.exist(),
  then: Joi.optional(),
  otherwise: Joi.required()
})

Если email указан, телефон становится необязательным.

Альтернативная форма через alternatives

Для более сложных сценариев используется Joi.alternatives().conditional, позволяющий описывать множественные ветки логики.

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

  cardNumber: Joi.alternatives().conditional('paymentMethod', [
    {
      is: 'card',
      then: Joi.string().creditCard().required(),
      otherwise: Joi.forbidden()
    }
  ])
});

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

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

when работает не только на верхнем уровне, но и внутри вложенных структур.

const schema = Joi.object({
  user: Joi.object({
    type: Joi.string().valid('guest', 'registered'),
    email: Joi.string().email().when('type', {
      is: 'registered',
      then: Joi.required(),
      otherwise: Joi.forbidden()
    })
  })
});

Условие применяется относительно соседних полей внутри объекта.

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

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

const schema = Joi.object({
  deliveryType: Joi.string().valid('courier', 'pickup'),

  address: Joi.string().when('deliveryType', {
    is: 'courier',
    then: Joi.required(),
    otherwise: Joi.optional()
  })
});

Поле адреса требуется только при доставке курьером.

Сложные условия с массивами

Условная логика применяется и к массивам, включая проверку элементов.

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

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

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

Множественные условия

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

const schema = Joi.object({
  country: Joi.string(),
  age: Joi.number(),

  drivingLicense: Joi.string().when('country', {
    is: 'US',
    then: Joi.when('age', {
      is: Joi.number().min(16),
      then: Joi.required(),
      otherwise: Joi.forbidden()
    }),
    otherwise: Joi.optional()
  })
});

Такая структура позволяет строить вложенную бизнес-логику.

Использование switch-логики через when с массивом

Можно задавать разные ветки условий для одного поля.

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

  comment: Joi.string().when('status', [
    {
      is: 'rejected',
      then: Joi.required()
    },
    {
      is: 'approved',
      then: Joi.optional()
    }
  ])
});

Каждое условие проверяется последовательно.

Взаимозависимость нескольких полей

Иногда логика зависит сразу от нескольких значений.

const schema = Joi.object({
  hasPassport: Joi.boolean(),
  country: Joi.string(),

  visaNumber: Joi.string().when('hasPassport', {
    is: true,
    then: Joi.when('country', {
      is: 'other',
      then: Joi.required()
    })
  })
});

Здесь условия комбинируются через вложенные when.

Частые паттерны использования

Подтверждение пароля:

confirm: Joi.string().valid(Joi.ref('password')).required()

Ролевой доступ:

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

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

contactMethod: Joi.string().valid('email', 'phone'),

email: Joi.string().email().when('contactMethod', {
  is: 'email',
  then: Joi.required()
}),

phone: Joi.string().when('contactMethod', {
  is: 'phone',
  then: Joi.required()
})

Особенности и нюансы поведения

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

Использование Joi.ref предпочтительно, когда требуется строгая привязка к значению другого поля без повторного указания имени.

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