Rules и правила валидации

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


Принцип построения правил

Каждый тип данных в Joi представляет собой объект схемы, к которому применяются методы-правила. Внутренне схема накапливает набор ограничений, которые затем интерпретируются во время validate().

const schema = Joi.string().min(3).max(30).required();

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

  • минимальная длина строки
  • максимальная длина строки
  • обязательное присутствие значения

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


Правила для строк

Строковые схемы содержат наиболее богатый набор ограничений.

Ограничения длины

Joi.string().min(5).max(20)
Joi.string().length(10)
  • min(n) — минимальная длина
  • max(n) — максимальная длина
  • length(n) — строго фиксированная длина

Формат и шаблоны

Joi.string().pattern(/^[a-z]+$/)

Правило pattern задаёт регулярное выражение, которому должна соответствовать строка.

Дополнительные специализированные правила:

Joi.string().email()
Joi.string().uri()
Joi.string().guid()

Эти правила реализуют часто используемые проверки форматов.

Преобразования строк

Joi.string().trim()
Joi.string().lowercase()
Joi.string().uppercase()

Эти правила не только валидируют, но и изменяют входное значение до проверки или после неё (в зависимости от конфигурации convert).

Разрешённые и запрещённые значения

Joi.string().valid('admin', 'user', 'guest')
Joi.string().invalid('root')
  • valid() задаёт допустимые значения
  • invalid() исключает конкретные значения

Правила для чисел

Числовые схемы поддерживают математические ограничения и типизацию.

Диапазоны

Joi.number().min(0).max(100)
  • min() — нижняя граница
  • max() — верхняя граница

Тип числа

Joi.number().integer()

Гарантирует отсутствие дробной части.

Знак числа

Joi.number().positive()
Joi.number().negative()

Ограничивает знак значения.

Кратность

Joi.number().multiple(5)

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

Точность

Joi.number().precision(2)

Ограничивает количество знаков после запятой.


Правила для булевых значений

Joi.boolean().truthy('yes').falsy('no')

Булевы схемы могут расширять допустимые представления истины и лжи.


Правила для дат

Joi.date().min('2020-01-01').max('2026-01-01')

Дополнительные ограничения:

Joi.date().iso()
Joi.date().timestamp()
  • iso() требует ISO-формат
  • timestamp() допускает Unix-время

Правила для массивов

Массивы используют структурные ограничения элементов.

Размер массива

Joi.array().min(1).max(10)
Joi.array().length(3)

Содержимое

Joi.array().items(Joi.string(), Joi.number())

Определяет допустимые типы элементов.

Уникальность

Joi.array().unique()

Исключает дублирование элементов.

Порядок элементов

Joi.array().ordered(
  Joi.string(),
  Joi.number()
)

Фиксирует позиционную структуру массива.

Проверка наличия

Joi.array().has(Joi.string().valid('admin'))

Правила для объектов

Объектные схемы задают структуру данных.

Определение полей

Joi.object({
  name: Joi.string().required(),
  age: Joi.number()
})

Управление неизвестными полями

Joi.object().unknown(true)

Разрешает дополнительные ключи.

Joi.object().unknown(false)

Запрещает поля, не описанные в схеме.

Логические зависимости

Joi.object().with('password', 'username')
Joi.object().without('token', 'password')
  • with() — обязательное совместное присутствие
  • without() — взаимоисключение

XOR, OR, AND

Joi.object().xor('a', 'b')
Joi.object().or('a', 'b')
Joi.object().and('a', 'b')
  • xor — только одно поле
  • or — хотя бы одно
  • and — все одновременно

Условные правила

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

Joi.object({
  role: Joi.string(),
  access: Joi.string().when('role', {
    is: 'admin',
    then: Joi.valid('full'),
    otherwise: Joi.valid('limited')
  })
})

Также поддерживаются альтернативные схемы:

Joi.alternatives().try(
  Joi.string(),
  Joi.number()
)

Общие правила (any)

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

Обязательность

Joi.any().required()
Joi.any().optional()
Joi.any().forbidden()

Значения по умолчанию

Joi.any().default('value')

Разрешённые и запрещённые значения

Joi.any().valid('a', 'b')
Joi.any().invalid('x')
Joi.any().allow(null)

Очистка значения

Joi.any().strip()

Полностью удаляет поле из результата.


Приоритет присутствия (presence rules)

Глобальная настройка поведения обязательности:

Joi.object().presence('required')

Возможные режимы:

  • required
  • optional
  • forbidden

Пользовательские правила

Механизм расширения логики через custom.

Joi.string().custom((value, helpers) => {
  if (value === 'bad') {
    return helpers.error('string.invalid');
  }
  return value;
})

Позволяет реализовать произвольные проверки, не ограниченные встроенными правилами.


Расширение схем

Joi поддерживает создание новых типов через extend.

const customJoi = Joi.extend((joi) => ({
  type: 'positiveInt',
  base: joi.number().integer().min(1),
  rules: {
    even: {
      validate(value, helpers) {
        if (value % 2 !== 0) {
          return helpers.error('number.even');
        }
        return value;
      }
    }
  }
}));

Настройки валидации

Поведение правил изменяется через параметры validate.

Joi.validate(data, schema, {
  abortEarly: false,
  convert: true,
  allowUnknown: true,
  stripUnknown: true
});

Основные опции:

  • abortEarly — остановка при первой ошибке
  • convert — автоматическое приведение типов
  • allowUnknown — разрешение лишних полей
  • stripUnknown — удаление неизвестных полей
  • presence — глобальная обязательность

Кастомизация сообщений ошибок

Каждое правило может сопровождаться персонализированными сообщениями:

Joi.string().min(5).messages({
  'string.min': 'Слишком короткая строка'
})

Ошибки привязываются к конкретным кодам правил.


Композиция правил

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

Joi.string()
  .trim()
  .min(3)
  .max(10)
  .pattern(/^[a-z]+$/)
  .required()

Каждый вызов добавляет новое ограничение, формируя итоговую модель данных.


Типичные ограничения и поведение правил

  • Правила не перезаписывают друг друга, а агрегируются
  • Некоторые правила выполняют преобразование, а не только проверку
  • Условные конструкции изменяют структуру схемы во время выполнения
  • Пользовательские правила интегрируются в общий поток валидации
  • Ошибки формируются на уровне конкретных нарушенных правил

Расширенные сценарии использования правил

Комбинации правил позволяют строить сложные схемы:

  • динамическая зависимость полей через when
  • строгая типизация объектов через object().keys()
  • ограничение бизнес-логики через valid/invalid
  • фильтрация данных через strip
  • адаптация входных значений через default и преобразования строк

Эта система делает Joi не просто инструментом проверки типов, а декларативным механизмом описания контрактов данных.