Схема в Joi представляет собой описание структуры данных, которое может быть выражено как обычный объект, функция-конструктор Joi или результат предварительной компиляции. Компиляция схемы переводит декларативное описание в оптимизированное внутреннее представление, пригодное для многократного использования при валидации данных.
Каждый вызов методов Joi (например, Joi.string(),
Joi.object(), Joi.number()) формирует объект
схемы, содержащий набор правил, метаданные и ссылки на валидаторы. До
момента использования схема остаётся гибкой и может изменяться через
цепочку методов (min(), max(),
required(), pattern() и другие).
При валидации Joi выполняет процесс нормализации схемы, приводя её к внутреннему формату. Этот этап включает:
alternatives, when,
switch).Компиляция обеспечивает сокращение повторяющихся вычислений при многократной валидации одинаковых структур данных.
В Joi существует механизм compile, который позволяет
явно преобразовать описание схемы в готовую к использованию форму:
const schema = Joi.compile({
name: Joi.string().min(3).max(30),
age: Joi.number().integer().min(0)
});
В этом случае объект проходит полную обработку на этапе компиляции,
после чего создаётся оптимизированная схема, которая уже не требует
дополнительного разбора при каждом вызове validate.
Компиляция выполняет несколько задач:
Joi выполняет компиляцию автоматически при первом использовании схемы
в validate. Это означает, что следующий код:
const schema = Joi.object({
id: Joi.number().required(),
title: Joi.string()
});
schema.validate({ id: 1, title: 'A' });
приводит к неявной компиляции при первом вызове валидации.
Явная компиляция через Joi.compile позволяет перенести
этот процесс на этап инициализации, снижая нагрузку на горячие пути
выполнения приложения.
Raw-схема представляет собой структуру, в которой правила описаны декларативно. Она может содержать:
.custom();.when();.ref().Скомпилированная схема преобразуется в оптимизированное дерево правил, где:
Это снижает количество операций во время валидации, особенно при повторных вызовах.
Наиболее заметный эффект компиляции проявляется при работе с объектами и массивами.
const schema = Joi.compile({
user: {
id: Joi.number().required(),
profile: {
name: Joi.string(),
age: Joi.number()
}
}
});
В процессе компиляции происходит рекурсивный обход структуры. Каждый вложенный объект преобразуется в отдельную схему, а связи между уровнями фиксируются в дереве валидации.
const schema = Joi.compile({
tags: Joi.array().items(Joi.string().min(2))
});
Для массивов создаётся единый валидатор элементов, который применяется ко всем элементам без повторного анализа описания типа на каждом шаге.
Скомпилированная схема становится неизменяемой структурой, что позволяет безопасно использовать её в разных частях приложения без риска модификации.
Повторное использование снижает накладные расходы:
Особенно это заметно при высоконагруженных API, где одна и та же схема используется тысячами запросов.
Компиляция влияет на два основных этапа:
Инициализация схемы Включает разбор, построение дерева правил и нормализацию.
Валидация данных Использует уже готовую структуру без дополнительного разбора.
При отсутствии явной компиляции первый вызов validate
включает оба этапа, что увеличивает задержку. При предварительной
компиляции первый этап выполняется один раз, а последующие вызовы
ограничиваются только проверкой данных.
Условные конструкции в Joi (when,
alternatives, switch) требуют дополнительной
обработки во время компиляции.
const schema = Joi.compile({
role: Joi.string(),
access: Joi.when('role', {
is: 'admin',
then: Joi.string().valid('all'),
otherwise: Joi.string().valid('limited')
})
});
Во время компиляции формируется структура зависимостей между полями. Это позволяет во время валидации быстро определять активную ветку без повторного анализа условий.
Joi поддерживает ссылки через .ref():
const schema = Joi.compile({
password: Joi.string(),
confirm: Joi.any().valid(Joi.ref('password'))
});
При компиляции ссылки разрешаются в специальные внутренние объекты, которые указывают на путь валидации. Это избавляет от необходимости повторного поиска значения во время проверки.
При использовании Joi.extend() создаются
пользовательские типы:
const customJoi = Joi.extend((joi) => ({
type: 'positive',
base: joi.number(),
validate(value, helpers) {
if (value <= 0) {
return { value, errors: helpers.error('number.positive') };
}
}
}));
Такие расширения также участвуют в компиляции. Их логика фиксируется в момент создания схемы, а затем включается в общее дерево валидаторов.
Joi использует внутреннее кэширование для уже скомпилированных схем. Если схема не изменяется, повторная компиляция не выполняется.
Кэш зависит от:
Изменение схемы после компиляции приводит к созданию новой внутренней версии.
При работе с вложенными объектами глубиной в несколько уровней компиляция выполняет рекурсивную оптимизацию:
Это особенно важно для API с сложными DTO-структурами.
Некоторые ошибки выявляются уже во время компиляции схемы:
alternatives;Такие ошибки фиксируются до начала обработки данных, что позволяет исключить часть runtime-ошибок.
Хотя Joi не является системой статической типизации, компиляция схемы формирует строгую модель данных в runtime. После компиляции структура становится фиксированной, а допустимые значения определяются набором правил.
Это позволяет:
Во время компиляции порядок цепочки методов фиксируется:
Joi.string().min(3).max(10).trim()
Этот порядок преобразуется в последовательность операций, которая сохраняется в скомпилированной структуре. Изменение порядка приводит к изменению итогового дерева правил, так как Joi исполняет валидаторы последовательно.
Компиляция схем становится ключевым элементом при построении слоёв валидации:
Такой подход уменьшает нагрузку на runtime и делает поведение валидации стабильным и предсказуемым при масштабировании.