В Joi схема валидации традиционно описывается как статическая структура, однако значительная часть задач требует поведения, зависящего от контекста выполнения. Динамические значения через функции позволяют вычислять ограничения, значения по умолчанию, сообщения об ошибках и даже саму структуру схемы в момент валидации.
Ключевая идея заключается в том, что вместо фиксированного значения в схему передаётся функция, которая выполняется в момент обработки данных и возвращает актуальный результат.
.default()Метод .default() поддерживает передачу функции, что
делает возможным генерацию значения на основе внешних факторов или
входных данных.
const schema = Joi.object({
createdAt: Joi.date().default(() => new Date())
});
Функция выполняется при каждой валидации, если значение отсутствует. Это принципиально отличается от статического значения:
// ❌ фиксированное значение вычисляется один раз при объявлении схемы
Joi.date().default(new Date())
Динамическая форма особенно полезна при работе с временными метками, идентификаторами и производными значениями.
.default((value, helpers) => {})Функция может принимать параметры контекста, позволяющие учитывать входные данные.
const schema = Joi.object({
fullName: Joi.string(),
slug: Joi.string().default((value, helpers) => {
const { fullName } = helpers.state.ancestors[0];
return fullName.toLowerCase().replace(/\s+/g, '-');
})
});
Здесь значение slug вычисляется на основе другого поля
объекта. Такой подход используется для производных данных, где одно поле
зависит от другого.
.custom() для произвольной логикиМетод .custom() позволяет внедрять функцию в процесс
валидации. Это один из наиболее гибких инструментов Joi.
const schema = Joi.number().custom((value, helpers) => {
if (value % 2 !== 0) {
return helpers.error('number.even');
}
return value;
});
Функция получает текущее значение и объект helpers,
через который можно:
helpers.errorДополнительно возможно использование внешнего состояния:
const schema = Joi.string().custom((value, helpers) => {
const banned = helpers.prefs.context?.bannedWords || [];
if (banned.includes(value)) {
return helpers.error('string.banned');
}
return value;
});
.lazy()Когда структура данных зависит от входного значения, используется
Joi.lazy().
const schema = Joi.lazy(value => {
if (typeof value === 'number') {
return Joi.number().min(0);
}
return Joi.string().max(100);
});
Функция вызывается в момент валидации и возвращает схему, соответствующую текущему значению.
Типичные сценарии:
.when()Хотя .when() часто используется со ссылками, он также
поддерживает функциональный стиль через динамическое определение
условий.
const schema = Joi.object({
role: Joi.string(),
accessLevel: Joi.number().when('role', {
is: (role) => role === 'admin',
then: Joi.number().min(10),
otherwise: Joi.number().max(5)
})
});
Функция в is позволяет строить сложные условия,
выходящие за рамки простого сравнения.
Функции применяются и для формирования сообщений, зависящих от контекста.
const schema = Joi.number().min(10).messages({
'number.min': (context) => `Значение ${context.value} меньше допустимого`
});
Контекст ошибки содержит:
Это позволяет формировать адаптивные сообщения, учитывающие конкретную ситуацию.
prefs.contextОдним из ключевых механизмов передачи динамических данных является контекст валидации.
const schema = Joi.string().custom((value, helpers) => {
const locale = helpers.prefs.context?.locale;
if (locale === 'ru' && value.length < 3) {
return helpers.error('string.tooShort');
}
return value;
});
Контекст задаётся при вызове:
schema.validate(data, {
context: { locale: 'ru' }
});
Это позволяет отделить бизнес-логику от схемы и передавать внешние параметры без изменения самой структуры.
Функции используются также в связке с ленивыми значениями для построения сложных конфигураций:
const schema = Joi.object({
config: Joi.lazy(() => {
const env = process.env.NODE_ENV;
if (env === 'production') {
return Joi.object({
cache: Joi.boolean().valid(true)
});
}
return Joi.object({
cache: Joi.boolean()
});
})
});
Такой подход позволяет адаптировать схему под среду выполнения без дублирования логики.
Функции в Joi выполняются в строго определённых точках:
custom)lazy)Важно учитывать, что:
Функциональные возможности Joi можно комбинировать для создания многоуровневой логики.
const schema = Joi.object({
type: Joi.string(),
data: Joi.lazy((value, helpers) => {
const parent = helpers.state.ancestors[0];
if (parent.type === 'A') {
return Joi.object({
value: Joi.number().default(() => Math.random() * 100)
});
}
return Joi.object({
value: Joi.string().default(() => 'default')
});
})
});
Здесь одновременно используются:
lazy для выбора структурыdefault с функцией для генерации значенийФункциональный подход в Joi превращает схему из статического описания в вычисляемую систему правил. Это позволяет строить:
Использование функций становится центральным механизмом при работе со сложными сценариями, где фиксированные правила оказываются недостаточными.