Joi version: 5.1
Hello. I'm trying to achieve something like this:
{
'a': Joi.string().valid('foo', 'bar'),
'b': Joi.number()
.when('a', { is: 'foo', then: Joi.required() })
.when('a', { is: 'bar', then: Joi.forbidden() })
}
However stepping through the code, it appears to exit after the first when condition, whether . I added the following unit test to test/any.js. The first set of tests is a simple example: the second when clause making the field forbidden is ignored. The second set of tests is based on the example in conditionalRequire.js, and fails erroneously on the last two tests.
it('supports multiple when clauses', function (done) {
// very simple test
var schema = {
a: Joi.string().valid('foo', 'bar'),
b: Joi.number()
.when('a', { is: 'foo', then: Joi.required() })
.when('a', { is: 'bar', then: Joi.forbidden() })
};
Helper.validate(schema, [
[{ a: 'foo' }, false], // a is required: pass
[{ a: 'bar' }, true], // a is forbidden: pass
[{ a: 'foo', b: 10 }, true], // a is required: pass
[{ a: 'bar', b: 10 }, false] // a is forbidden: FAIL
]);
// test based on examples/conditionalRequire.js
// This is a valid value for integer rating 1 - 5
var intRating = Joi.number().integer().min(1).max(5);
var schema = Joi.object().keys({
// Do you know any French people? yes or no (required)
q1: Joi.boolean().required(),
// Do you know any Parisians? yes or no (required if answered yes in q1)
q2: Joi.boolean()
.when('q1', { is: true, then: Joi.required() }),
// How many french in paris do you know? 1-6, 6-10, 11-50 or 50+ (required if answered yes in q2)
q3: Joi.string()
.when('q2', { is: true, then: Joi.valid('1-5', '6-10', '11-50', '50+').required() }),
// Rate 20% of most friendly Parisians, from how many people you know answered in q3, individually on 1-5 rating
q4: Joi.array()
.when('q3', {is: '1-5', then: Joi.array().min(0).max(1).includes(intRating).required() })
.when('q3', {is: '6-10', then: Joi.forbidden() })
.when('q3', {is: '11-50', then: Joi.array().min(0).max(10).includes(intRating).required() })
.when('q3', {is: '50+' , then: Joi.array().min(0).includes(intRating).required() })
});
Helper.validate(schema, [
[{ // q4 is required: pass
q1: true,
q2: true,
q3: '1-5',
q4: [5]
}, true],
[{ // q4 is forbidden: pass
q1: true,
q2: true,
q3: '6-10'
}, true],
[{ // q4 is required: pass
q1: true,
q2: true,
q3: '11-50',
q4: [5]
}, true],
[{ // q4 is required: pass
q1: true,
q2: true,
q3: '50+',
q4: [2]
}, true],
[{ // q4 is forbidden: FAIL
q1: true,
q2: true,
q3: '6-10',
q4: [5]
}, false],
[{ // q4 is required: FAIL
q1: true,
q2: true,
q3: '11-50'
}, false]
], done);
});
});
After a little digging I suspect the problem is something to do with any.js, line 268: when setting otherwise, it defaults to this if no otherwise clause is specified by the user. This means that down the food chain in alternatives.js line 36, after it fails to match the current ref, it thinks it has an otherwise clause to execute instead of continuing on to the next when.
If I'm doing this wrong or misunderstanding the intention of the feature, please educate me! It'd be lovely to get multiple conditions working.
:+1: I've just come across this myself and really need the multiple when syntax to work. For me a work around, albeit a laborious one, is to create many schemas and look at a property to decide which schema to use... I've only got about 8 schemas to implement but would prefer one where I can specify the multiple when clauses.
I've been thinking about it a lot, I'm not sure when can support this kind of construct. What happens if you have multiple otherwise in those ?
Maybe it could check for conflicts and error accordingly? So for example:
var schema = {
a: Joi.string().valid('foo', 'bar', 'baz'),
b: Joi.string()
.when('a', { is: 'foo', then: Joi.hostname(), otherwise: Joi.email() })
.when('a', { is: 'bar', then: Joi.creditCard(), otherwise: Joi.guid() })
};
This is then a conflict since if a is baz what should b be?
_disclaimer: I wrote that without checking the syntax!_
Yeah, I think trying to support otherwise clauses for chained whens might be hard. I mean the above is the equivalent of:
if (a === 'foo') {
Joi.hostname();
} else {
Joi.email();
}
if (a === 'bar') {
Joi.creditCard();
} else {
Joi.guid();
}
Which is obviously counter-intuitive at best, I would go so far as to say it's flawed logic. What we'd be shooting for instead would be more like:
if (a === 'foo') {
Joi.hostname();
} else if (a === 'bar') {
Joi.creditCard();
} else {
Joi.guid();
}
Are the when clauses aware of their contexts-- i.e. that they're chained, and what the preceding clause looks like? Is there a chance we could do something simple like declaring that for multiple whens on the same ref, only the last may have an otherwise? (I realise that's simple in terms of requirements, probably not so much in implementation.)
Alternatively I guess we could do a whole new construct... but that seems overkill.
You can try with alternatives, locking the property you want to use for comparison
Joi.alternatives.try({
a: Joi.string().valid('foo'),
b: Joi.number().required()
}, {
a: Joi.string().valid('bar'),
b: Joi.number().forbidden()
})
_disclaimer: I never really tried it :stuck_out_tongue:_
Given the first example, why not do this?
{
'a': Joi.string().valid('foo', 'bar'),
'b': Joi.number()
.when('a', {
is: 'foo',
then: Joi.required(),
otherwise: Joi.alternatives().when('a', { is: 'bar', then: Joi.forbidden() })
})
}
I've had to do something similar in one of my more complex models.
Just wondering, did any of you had a look at https://github.com/hapijs/joi/blob/master/examples/multipleWhen.js ?
Is it too much trouble to keep your base object in a variable and re-use it in all your alternatives ?
Definitely didn't know that multiple whens could be used like that. Would probably be good to update the docs to show that it can be done.
Inactive issue, closing.
hi @Marsup, i've tried the conditional require example and the validation is incorrect when i change response.q3 = '6-10'; response.q4 = undefined;, Joi should throw a error, because q4 must be a required array when q3 is '6-10'. Seems like multiple when conditions are really broken, because it only takes the first condition and ignore the others..
i've found Joi ignores the others when conditions because there is some logic in any.when method that default to this when no then/otherwise is passed.
this causes the options of the first call of any.when
->
q4: Joi.array().when('q3', {is: '1-5', then: Joi.array().min(0).max(1).items(intRating).required() })
to be
{ then: Joi.array().min(0).max(1).items(intRating).required(), otherwise: this // "otherwise" is set to "this" even if no specified in the schema }
and when the alternatives are validated joi stops on the first alternative because of the implicit otherwise condition.
this problem only happens when validating multiple when conditions coming from any type
The problem might be the example, not joi. Multiple whens work with the syntax I linked a few comments before. I don't have time to fix the example right now so if you do, PR welcome.
multiple when works when schema is started with alternatives.when, but when coming from a any type -> any.when, it doesn't work.
i can send a PR changing q4: Joi.array().when(.....) -> to -> q4: Joi.alternatives().when(....) for the example
my use case when multiple conditions doesn't work is to make a schema that validates a docName property based on type property, by default docName should be a optional string, when type is 07 or 08, docName must be a required string.
var schema = {
type: Joi.string().required(),
docName: Joi.string()
.when('type', { is: '07', then: Joi.required() })
.when('type', { is: '08', then: Joi.required() })
};
unfortunately this doesn't work, as i commented before multiple when conditions doesn't work coming from any type, because of the implicit otherwhise condition added to it.
i can't use alternatives here because i need to modify the type (make it required) based on a condition
Just a quick tip, is can be any joi schema, so here you don't need multiple ones, just is: Joi.valid('07', '08').
Oh god, you're right!!, i will use that for my schema, but maybe at least you should open the issue to keep track of the issue for multiple when coming from any type
Anyway thnks for your time! :)
Most helpful comment
Just a quick tip,
iscan be any joi schema, so here you don't need multiple ones, justis: Joi.valid('07', '08').