Reputation: 41223
I have two validations to perform on the same payload:
When hasSalary
is true, either monthlySalary
or annualSalary
must be present.
When hasCosts
is true, either monthlyCosts
or annualCosts
must be present.
I have coded this as:
Joi.object({
hasSalary: Joi.boolean(),
monthlySalary: Joi.number(),
annualSalary: Joi.number(),
hasCosts: Joi.boolean(),
monthlyCosts: Joi.number(),
annualCosts: Joi.number(),
})
.when(
Joi.object({ hasSalary: Joi.boolean().valid(true).required() }),
{
then: Joi.object().xor('monthlySalary', 'annualSalary')
}
)
.when(
Joi.object({ hasCosts: Joi.boolean().valid(true).required() }),
{
then: Joi.object().xor('monthlyCosts', 'annualCosts')
}
);
This correctly gives a validation error for: { hasSalary: true }
:
message: '"value" must contain at least one of [monthlySalary, annualSalary]'
... and for { hasCosts: true }
:
message: '"value" must contain at least one of [monthlyCosts, annualCosts]'
... but doesn't work as I expected when both the booleans are true
, and the second when
's constraints are not met:
{
hasSalary: true,
monthlySalary: 300,
hasCosts: true,
}
I hoped for "value" must contain at least one of [monthlyCosts, annualCosts]
here, but instead I got a clean validation with no error.
I think I understand what's happening - chaining when
s is creating a series of guards, and the first matching one wins.
So what construct can I use in Joi (ideally version 15) to achieve what I wanted?
Upvotes: 3
Views: 1214
Reputation: 2813
With the newest version Joi
17.2.1 you don't have this problem (multiple when conditions resolve correctly)
But with Joi
15.1.1 you can use the following workaround:
const Joi = require('@hapi/joi');
const one = Joi.object({
hasSalary: Joi.boolean().valid(true),
monthlySalary: Joi.number(),
annualSalary: Joi.number(),
}).xor('monthlySalary', 'annualSalary');
const two = Joi.object({
hasSalary: Joi.boolean().valid(false),
monthlySalary: Joi.number(),
annualSalary: Joi.number(),
});
const three = Joi.object({
hasCosts: Joi.boolean().valid(true),
monthlyCosts: Joi.number(),
annualCosts: Joi.number(),
}).xor('monthlyCosts', 'annualCosts');
const four = Joi.object({
hasCosts: Joi.boolean().valid(false),
monthlyCosts: Joi.number(),
annualCosts: Joi.number(),
});
const one_three = one.concat(three);
const one_four = one.concat(four);
const two_three = two.concat(three);
const two_four = two.concat(four);
const schema = Joi.alternatives().try(
one,
two,
three,
four,
one_three,
one_four,
two_three,
two_four,
);
Run some tests:
// works
const data1 = {
hasSalary: true,
monthlySalary: 2000,
};
console.log(schema.validate(data1).error);
// works
const data2 = {
hasSalary: false,
};
console.log(schema.validate(data2).error);
// works
const data3 = {
hasCosts: true,
monthlyCosts: 300,
};
console.log(schema.validate(data3).error);
// works
const data4 = {
hasCosts: false,
};
console.log(schema.validate(data4).error);
// works
const data5 = {
hasSalary: true,
monthlySalary: 2000,
hasCosts: true,
monthlyCosts: 300,
};
console.log(schema.validate(data5).error);
// works
const data6 = {
hasSalary: false,
hasCosts: true,
monthlyCosts: 300,
};
console.log(schema.validate(data6).error);
// works
const data7 = {
hasSalary: true,
monthlySalary: 2000,
hasCosts: false,
};
console.log(schema.validate(data7).error);
// works
const data8 = {
hasSalary: false,
hasCosts: false,
};
console.log(schema.validate(data8).error);
// error
const data9 = {
hasSalary: true
};
console.log(schema.validate(data9).error.message)
// error
const data10 = {
hasSalary: true,
annualSalary: 1000,
hasCosts: true,
};
console.log(schema.validate(data10).error.message)
Upvotes: 1