Кросс-полевая валидация в Joi применяется в случаях, когда корректность одного поля зависит от значения другого. В отличие от простой валидации отдельных значений, здесь проверяется согласованность структуры объекта в целом: соответствие диапазонов, совпадение парных значений, взаимная обязательность или исключение полей.
Основой механизма выступают ссылки на другие поля, условные схемы и набор зависимостей между ключами.
Joi.refСсылки позволяют обращаться к значению другого поля внутри схемы. Это базовый инструмент кросс-полевой логики.
const schema = Joi.object({
password: Joi.string().min(8).required(),
confirmPassword: Joi.string().valid(Joi.ref('password')).required()
});
В данном случае значение confirmPassword должно строго
совпадать с password.
Ссылки могут использоваться не только в valid, но и в
преобразованиях:
Joi.number().min(Joi.ref('minValue'))
или в условиях:
Joi.string().when('role', {
is: 'admin',
then: Joi.required(),
otherwise: Joi.optional()
});
whenМетод when реализует динамическое поведение схемы в
зависимости от других полей.
const schema = Joi.object({
type: Joi.string().valid('individual', 'company').required(),
companyName: Joi.string().when('type', {
is: 'company',
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
const schema = Joi.object({
minAge: Joi.number().required(),
maxAge: Joi.number().when('minAge', {
is: Joi.number().required(),
then: Joi.number().min(Joi.ref('minAge'))
})
});
Такой подход обеспечивает согласованность диапазонов внутри одного объекта.
Joi предоставляет набор декларативных правил для описания связей между полями.
withТребует присутствия одного поля при наличии другого.
Joi.object({
cardNumber: Joi.string(),
cvc: Joi.string()
}).with('cardNumber', 'cvc');
withoutЗапрещает совместное использование полей.
Joi.object({
token: Joi.string(),
password: Joi.string()
}).without('token', 'password');
xorРазрешает только одно из полей.
Joi.object({
email: Joi.string().email(),
phone: Joi.string()
}).xor('email', 'phone');
orТребует хотя бы одно поле из набора.
Joi.object({
email: Joi.string().email(),
phone: Joi.string()
}).or('email', 'phone');
andТребует совместного присутствия всех указанных полей.
Joi.object({
a: Joi.string(),
b: Joi.string(),
c: Joi.string()
}).and('a', 'b', 'c');
nandЗапрещает одновременное наличие всех полей.
Joi.object({
start: Joi.date(),
end: Joi.date()
}).nand('start', 'end');
Кросс-полевая логика часто используется для проверки равенства или порядка значений.
Joi.object({
password: Joi.string().required(),
repeat: Joi.string().valid(Joi.ref('password'))
});
Joi.object({
from: Joi.number().required(),
to: Joi.number().greater(Joi.ref('from'))
});
Joi.object({
startDate: Joi.date().required(),
endDate: Joi.date().greater(Joi.ref('startDate'))
});
Такие конструкции формируют строгую согласованность временных и числовых диапазонов.
При работе с диапазонами часто требуется учитывать сразу несколько зависимостей:
Joi.object({
minPrice: Joi.number().required(),
maxPrice: Joi.number()
.required()
.min(Joi.ref('minPrice'))
.less(10000)
});
Комбинация min, max, greater,
less и ref позволяет задавать сложные
ограничения без кастомного кода.
context для динамических зависимостейКонтекстные значения позволяют внедрять внешние параметры в схему.
const schema = Joi.object({
age: Joi.number().min(Joi.ref('$minAge'))
});
При валидации:
schema.validate(data, { context: { minAge: 18 } });
Контекст часто используется для конфигурационных зависимостей, где значение не является частью проверяемого объекта, но влияет на его валидность.
alternatives()
для сложных кросс-структурКогда логика выходит за рамки простых зависимостей, применяется
alternatives.
const schema = Joi.object({
payment: Joi.alternatives().try(
Joi.object({
type: Joi.valid('card'),
cardNumber: Joi.string().required()
}),
Joi.object({
type: Joi.valid('cash'),
cashOnly: Joi.boolean().valid(true)
})
)
});
alternatives позволяет описывать взаимоисключающие
структуры внутри одного поля или объекта.
Joi.object({
password: Joi.string().min(8).required(),
confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});
Joi.object({
hasDiscount: Joi.boolean(),
discountCode: Joi.string().when('hasDiscount', {
is: true,
then: Joi.required(),
otherwise: Joi.forbidden()
})
});
Joi.object({
start: Joi.number().required(),
end: Joi.number().greater(Joi.ref('start'))
});
Joi.object({
username: Joi.string(),
email: Joi.string()
}).xor('username', 'email');
Использование ссылок без учёта преобразований приводит к
несоответствиям, особенно при convert: true, когда типы
приводятся автоматически.
Некорректное применение when с неполными условиями
создаёт ситуации, при которых часть веток схемы остаётся без явной
валидации.
Смешивание required() и forbidden() внутри
разных веток условной логики может приводить к неоднозначным схемам,
особенно при глубокой вложенности объектов.
В сложных моделях данных кросс-полевая валидация строится как комбинация:
ref)when)with, xor,
or)alternatives)context)Такая композиция позволяет описывать правила целостности данных декларативно, без ручной проверки состояния объекта после валидации.