Joi предоставляет механизм построения зависимых и условных правил, позволяющих изменять схему валидации в зависимости от значений других полей, контекста или внешних параметров. Сложные условия применяются в случаях, когда структура данных не является статичной и зависит от бизнес-логики.
.when()Основной инструмент построения зависимостей — метод
.when(). Он позволяет изменять схему поля в зависимости от
значения другого поля или собственного значения.
const schema = Joi.object({
type: Joi.string().valid('email', 'phone').required(),
value: Joi.when('type', {
is: 'email',
then: Joi.string().email().required(),
otherwise: Joi.string().pattern(/^\d{10,15}$/).required()
})
});
Логика:
type === 'email', поле value должно
быть emailswitchДля более сложных сценариев применяется массив условий:
const schema = Joi.object({
method: Joi.string().required(),
details: Joi.when('method', {
switch: [
{
is: 'card',
then: Joi.object({
cardNumber: Joi.string().creditCard().required(),
cvv: Joi.string().length(3).required()
})
},
{
is: 'paypal',
then: Joi.object({
email: Joi.string().email().required()
})
}
],
otherwise: Joi.forbidden()
})
});
Такой подход используется, когда набор структур сильно различается.
context)Условия могут зависеть не только от полей объекта, но и от внешнего контекста валидации:
const schema = Joi.object({
price: Joi.number().when('$currency', {
is: 'USD',
then: Joi.number().min(1),
otherwise: Joi.number().min(0.5)
})
});
schema.validate({ price: 10 }, { context: { currency: 'USD' } });
Контекст позволяет внедрять глобальные параметры валидации, не включая их в сам объект.
.and(), .or(),
.xor()Эти методы задают логические зависимости между полями объекта.
.and() — все или ничегоJoi.object({
a: Joi.number(),
b: Joi.number()
}).and('a', 'b');
Если присутствует a, то обязателен b, и
наоборот.
.or() — хотя бы одно
полеJoi.object({
email: Joi.string().email(),
phone: Joi.string()
}).or('email', 'phone');
Допустимо наличие одного или обоих полей.
.xor() — строго одно
полеJoi.object({
username: Joi.string(),
email: Joi.string().email()
}).xor('username', 'email');
Запрещает одновременное наличие обоих полей.
.nand() и .with().nand() — нельзя
использовать вместеJoi.object({
password: Joi.string(),
token: Joi.string()
}).nand('password', 'token');
Одновременное присутствие запрещено.
.with() — обязательная
связкаJoi.object({
cardNumber: Joi.string(),
cvv: Joi.string()
}).with('cardNumber', 'cvv');
Если есть cardNumber, требуется cvv.
.alter()Метод .alter() позволяет определять альтернативные схемы
для разных сценариев использования.
const schema = Joi.object({
password: Joi.string().min(6)
.alter({
register: (schema) => schema.min(8).required(),
login: (schema) => schema.required()
})
});
Применение:
schema.tailor('register').validate({ password: '12345678' });
.ruleset()Позволяет комбинировать правила с переопределением логики:
const schema = Joi.number()
.min(0)
.max(100)
.when('..level', {
is: 'strict',
then: Joi.number().min(10).max(90)
});
Используется для наложения дополнительных ограничений поверх базовых.
.presence()Условная обязательность управляется через
.presence():
Joi.object({
apiKey: Joi.string()
}).presence('optional');
Возможные значения:
requiredoptionalforbidden.ref()Ссылки позволяют обращаться к другим полям объекта:
const schema = Joi.object({
min: Joi.number().required(),
max: Joi.number().greater(Joi.ref('min')).required()
});
Здесь max зависит от значения min.
Сложные схемы часто комбинируются с .custom():
Joi.number().custom((value, helpers) => {
if (value < helpers.state.ancestors[0].min) {
return helpers.error('number.min');
}
return value;
});
Это позволяет реализовывать произвольную бизнес-логику, выходящую за рамки стандартных условий.
Условные правила могут применяться на глубоко вложенных структурах:
const schema = Joi.object({
user: Joi.object({
role: Joi.string(),
permissions: Joi.when('role', {
is: 'admin',
then: Joi.array().items(Joi.string()).min(1),
otherwise: Joi.forbidden()
})
})
});
Зависимости могут распространяться по всей иерархии объекта.
В реальных схемах часто объединяются несколько механизмов:
.when() для динамики.xor() и .or() для логики присутствия.ref() для межполевых сравнений.context() для внешних параметровconst schema = Joi.object({
mode: Joi.string().valid('create', 'update'),
id: Joi.when('mode', {
is: 'update',
then: Joi.number().required(),
otherwise: Joi.forbidden()
}),
email: Joi.string().email().when('mode', {
is: 'create',
then: Joi.required()
})
});
Такие схемы формируют декларативное описание бизнес-логики на уровне данных, исключая необходимость ручных проверок в коде приложения.