Try и порядок проверки

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

Ключевое различие между подходами проявляется при использовании schema.validate, schema.validateAsync и Joi.attempt.


Поведение schema.validate

Метод validate выполняет проверку без исключений:

const result = schema.validate(value);

if (result.error) {
  // обработка ошибки
}

Возвращаемая структура содержит:

  • value — преобразованное значение после всех правил
  • error — объект ошибки, если проверка не прошла

В этом режиме порядок обработки ошибок определяется параметрами схемы, в частности abortEarly.


Поведение schema.validateAsync

Асинхронная версия интегрируется с async/await и использует исключения:

try {
  const value = await schema.validateAsync(input);
} catch (err) {
  // обработка ошибки валидации
}

При любой ошибке выполнение прерывается, и управление передаётся в catch.


Использование Joi.attempt

Метод attempt реализует строгий режим:

const value = Joi.attempt(input, schema);

При нарушении правил сразу выбрасывается исключение. Это делает attempt эквивалентом синтаксического сокращения для validateAsync с обязательным throw.


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

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

Цепочка правил выполняется слева направо:

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

Фактический порядок проверки:

  1. приведение типа (если применимо)
  2. базовая проверка типа
  3. min(5)
  4. max(10)
  5. pattern(...)

Каждый этап может модифицировать значение или завершить выполнение с ошибкой.


Влияние abortEarly на порядок ошибок

Параметр abortEarly управляет тем, останавливается ли проверка при первой ошибке:

schema.validate(value, { abortEarly: true });

При значении true:

  • выполнение прекращается на первой ошибке
  • остальные правила не анализируются

При значении false:

  • выполняются все правила схемы
  • возвращается полный список ошибок

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


Порядок обработки типов и преобразований

Joi выполняет преобразования до финальной валидации:

Joi.number().integer().min(10)

Фактический порядок:

  1. попытка приведения к числу
  2. проверка, что значение — число
  3. проверка integer
  4. проверка min

Если преобразование невозможно, дальнейшие шаги не выполняются.


Критическая роль when и alternatives

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

when

Joi.object({
  a: Joi.number(),
  b: Joi.number().when('a', {
    is: 10,
    then: Joi.required()
  })
})

Здесь порядок становится зависимым от значения a:

  • сначала вычисляется a
  • затем определяется ветка для b
  • далее применяется соответствующая схема

alternatives

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

Проверка выполняется последовательно:

  1. проверка первой схемы
  2. если ошибка — переход ко второй
  3. выбор первой успешной

Порядок альтернатив критичен: первая подходящая схема завершает проверку.


Цепочки модификаторов и их влияние на порядок

Некоторые методы не просто проверяют значение, а изменяют его до следующего шага:

Joi.string()
  .trim()
  .lowercase()
  .min(3)

Фактический порядок:

  1. trim() — удаление пробелов
  2. lowercase() — преобразование регистра
  3. min(3) — проверка длины

Если бы min выполнялся раньше, результат мог бы отличаться, поэтому Joi строго фиксирует последовательность трансформаций.


Ошибки выполнения и момент генерации исключения

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

Пример:

Joi.string().min(5).max(10).pattern(/[0-9]+/)

При abortEarly: false возможны несколько ошибок:

  • нарушение min
  • несоответствие pattern
  • нарушение max

Их порядок совпадает с порядком объявления.


Влияние кастомных правил

custom функции встраиваются в общий пайплайн:

Joi.number().custom((value, helpers) => {
  if (value < 0) {
    return helpers.error('number.negative');
  }
  return value;
})

Порядок:

  1. стандартные проверки Joi
  2. кастомная функция
  3. последующие правила (если значение возвращено без ошибок)

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


Синхронная и асинхронная модель порядка

Синхронная проверка (validate) и асинхронная (validateAsync) используют одинаковую внутреннюю последовательность правил. Различие только в механизме доставки результата:

  • синхронный — возврат объекта
  • асинхронный — Promise и исключения

Порядок применения правил не зависит от выбранной модели.


Влияние опции presence

Параметр presence влияет на начальную стадию проверки:

  • required
  • optional
  • forbidden

Он обрабатывается до остальных правил, формируя базовое состояние валидности поля. Например:

Joi.string().required().min(3)

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


Итеративная модель проверки объектов

При проверке объектов Joi проходит по полям в порядке их описания в схеме:

Joi.object({
  a: Joi.number(),
  b: Joi.string(),
  c: Joi.boolean()
})

Порядок:

  1. a
  2. b
  3. c

При включённом abortEarly порядок напрямую определяет, какая ошибка будет первой.


Детали внутренней приоритетности правил

Некоторые типы правил имеют более высокий приоритет:

  • coercion (приведение типов)
  • presence
  • base type check
  • refinements (min, max, pattern)
  • custom rules
  • post-processing (trim, lowercase, default)

Этот порядок фиксирован и не зависит от расположения методов в цепочке, если они принадлежат разным категориям обработки.