Assert для сложных условий

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

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

Базовая форма использования:

import Joi from 'joi';

Joi.assert(42, Joi.number().integer());

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

Дополнительный аргумент позволяет задать пользовательское сообщение:

Joi.assert('text', Joi.number(), 'Ожидалось числовое значение');

Природа сложных условий в схемах Joi

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

Валидация перестаёт быть линейной, когда:

  • структура объекта зависит от значения одного из полей
  • поля взаимоисключают друг друга
  • обязательность зависит от внешнего контекста
  • данные допускают несколько допустимых форматов

Логические операторы схем

Joi предоставляет набор логических операторов для построения композитных правил.

AND, OR, XOR, NAND

Комбинации полей могут быть описаны через логические конструкции:

const schema = Joi.object({
  a: Joi.number(),
  b: Joi.number(),
  c: Joi.number()
}).and('a', 'b');

В данном случае a и b обязаны присутствовать одновременно.

Альтернативные условия:

Joi.object({
  a: Joi.number(),
  b: Joi.number()
}).or('a', 'b');

Смысл: хотя бы одно поле должно быть задано.

Исключающее поведение:

Joi.object({
  a: Joi.number(),
  b: Joi.number()
}).xor('a', 'b');

Допускается строго одно из полей.

Условная логика через when

Ключевой инструмент построения сложных условий — when, позволяющий изменять схему в зависимости от значения другого поля.

const schema = Joi.object({
  type: Joi.string().valid('email', 'phone').required(),
  value: Joi.string().when('type', {
    is: 'email',
    then: Joi.string().email(),
    otherwise: Joi.string().pattern(/^[0-9]+$/)
  })
});

Здесь структура value динамически меняется в зависимости от type.

Многоуровневые зависимости:

const schema = Joi.object({
  role: Joi.string(),
  access: Joi.number().when('role', {
    switch: [
      { is: 'admin', then: Joi.number().min(10) },
      { is: 'user', then: Joi.number().max(5) }
    ],
    otherwise: Joi.number().valid(0)
  })
});

Проверка взаимосвязанных полей

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

reference и context-aware проверки

const schema = Joi.object({
  min: Joi.number(),
  max: Joi.number().greater(Joi.ref('min'))
});

Здесь max зависит от значения min, создавая межполевую зависимость.

Дополнительные конструкции:

  • Joi.ref() — ссылка на другое поле
  • greater, less, min, max с динамическими аргументами

Альтернативные схемы через alternatives

Когда данные могут иметь несколько допустимых форм, применяется alternatives.

const schema = Joi.alternatives().try(
  Joi.string().email(),
  Joi.string().pattern(/^[0-9]+$/),
  Joi.object({
    id: Joi.number()
  })
);

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

Расширенный вариант:

Joi.alternatives().conditional('type', {
  switch: [
    { is: 'A', then: Joi.object({ a: Joi.number() }) },
    { is: 'B', then: Joi.object({ b: Joi.string() }) }
  ]
});

Кастомная логика через custom

Для сценариев, выходящих за рамки декларативных правил, применяется custom.

const schema = Joi.number().custom((value, helpers) => {
  if (value % 2 !== 0) {
    return helpers.error('number.even');
  }
  return value;
});

Кастомные правила интегрируются в систему ошибок Joi и могут комбинироваться с другими ограничениями.

Композиция схем

Сложные условия часто формируются через композицию:

const base = Joi.object({
  id: Joi.number().required()
});

const extended = base.keys({
  name: Joi.string().when('id', {
    is: Joi.number().min(10),
    then: Joi.required(),
    otherwise: Joi.optional()
  })
});

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

Применение assert в сложных сценариях

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

const schema = Joi.object({
  role: Joi.string().required(),
  permissions: Joi.array().items(Joi.string())
    .when('role', {
      is: 'admin',
      then: Joi.required(),
      otherwise: Joi.optional()
    })
});

Joi.assert(
  { role: 'admin', permissions: [] },
  schema
);

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