Coverage валидационных правил

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

Полное покрытие валидации формируется на уровне схемы и включает несколько взаимосвязанных слоёв:

  • типизация значения (string, number, object, array, boolean)
  • ограничения формата и диапазона
  • обязательность и допустимость отсутствия значения
  • условные зависимости между полями
  • вложенные структуры и рекурсивные схемы
  • кастомные проверки

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

Базовые типы и их покрытие

Строковые значения

Строковые схемы формируют наиболее насыщенную область правил:

Joi.string()
  .min(3)
  .max(30)
  .pattern(/^[a-zA-Z0-9]+$/)

Покрытие включает:

  • минимальную и максимальную длину
  • регулярные выражения
  • обязательное соответствие формату (email, uri, uuid через предустановленные схемы)
  • контроль пустых значений через empty

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

Числовые значения

Числовая область покрывается через диапазоны и дискретные ограничения:

Joi.number()
  .integer()
  .min(0)
  .max(100)
  .precision(2)

Покрытие включает:

  • ограничение диапазона
  • целочисленность
  • точность
  • кратность (multiple)

Особое значение имеет сочетание min/max с precision, так как оно определяет допустимое пространство значений с высокой плотностью ограничений.

Объекты и структурное покрытие

Объектные схемы формируют основу комплексной валидации:

Joi.object({
  id: Joi.number().required(),
  name: Joi.string().required(),
  profile: Joi.object({
    age: Joi.number().min(0),
    email: Joi.string().email()
  })
})

Покрытие включает:

  • обязательность ключей
  • вложенные схемы
  • строгую или гибкую структуру (unknown, fork, keys)
  • контроль лишних полей через unknown(false) или stripUnknown

Глубина вложенности определяет полноту структурного покрытия, где каждый уровень может иметь собственный набор правил.

Массивы и покрытие элементов

Массивы требуют двойного уровня валидации: самого контейнера и его элементов.

Joi.array()
  .items(Joi.string().min(2))
  .min(1)
  .max(10)
  .unique()

Покрытие включает:

  • ограничения длины массива
  • проверку уникальности элементов
  • типизацию элементов через items
  • поддержание однородных и неоднородных структур

В случае сложных массивов возможно комбинирование схем:

Joi.array().items(
  Joi.object({ type: Joi.string(), value: Joi.any() }),
  Joi.string()
)

Это создаёт полиморфное покрытие, при котором массив допускает разные формы элементов.

Условные правила и расширение покрытия

Условная логика расширяет пространство допустимых данных:

Joi.object({
  role: Joi.string().valid('admin', 'user'),
  permissions: Joi.when('role', {
    is: 'admin',
    then: Joi.array().items(Joi.string()),
    otherwise: Joi.forbidden()
  })
})

Механизмы:

  • when для зависимых правил
  • alternatives для альтернативных схем
  • switch для множественных условий

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

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

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

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

Кастомные правила и расширение покрытия

Расширение базового набора правил позволяет формировать доменно-ориентированную валидацию:

const custom = Joi.extend((joi) => ({
  type: 'evenNumber',
  base: joi.number(),
  validate(value, helpers) {
    if (value % 2 !== 0) {
      return { value, errors: helpers.error('number.even') }
    }
  }
}))

Такое расширение создаёт новые области покрытия, не ограниченные стандартной библиотекой.

Контроль присутствия значений

Модель присутствия значений определяет, насколько строго схема покрывает отсутствие данных:

  • required() — обязательное поле
  • optional() — допускается отсутствие
  • forbidden() — запрещённое поле
  • default() — значение по умолчанию

Комбинации этих правил определяют плотность покрытия в рамках схемы объекта.

Строгость и очистка данных

Механизмы контроля избыточных данных влияют на фактическое покрытие:

Joi.object({
  a: Joi.string()
}).unknown(false)

или

Joi.object({
  a: Joi.string()
}).options({ stripUnknown: true })

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

Контекстное покрытие и преобразования

Преобразования влияют на финальное пространство валидных значений:

  • trim() изменяет допустимые строковые формы
  • lowercase() и uppercase() нормализуют регистр
  • greater, less, isoDate формируют контекстные ограничения

Фактическое покрытие включает не только входные значения, но и их трансформированные формы после обработки.

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

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

Joi.object({
  user: Joi.object({
    profile: Joi.object({
      settings: Joi.object({
        theme: Joi.string()
      })
    })
  })
})

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

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

Схемы могут комбинироваться для расширения покрытия:

const base = Joi.object({ id: Joi.number() })
const extended = base.keys({
  name: Joi.string()
})

Композиция позволяет переиспользовать существующие правила без потери полноты ограничений.

Частичное и полное покрытие

Различие между частичной и полной валидацией определяется уровнем обязательности правил:

  • полное покрытие требует описания всех ключевых аспектов структуры
  • частичное покрытие допускает неопределённые области, оставляя их вне контроля

Это особенно заметно при работе с API-данными, где входные структуры могут эволюционировать.

Эдж-кейсы и пограничные состояния

Покрытие правил считается неполным без учёта пограничных значений:

  • пустые строки и массивы
  • нулевые значения
  • NaN и Infinity
  • null и undefined

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