Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Required based on value on other key #166

Closed
simme opened this issue Jan 30, 2014 · 13 comments
Closed

Required based on value on other key #166

simme opened this issue Jan 30, 2014 · 13 comments
Assignees
Labels
feature New functionality or improvement
Milestone

Comments

@simme
Copy link

simme commented Jan 30, 2014

I have the following JSON objects:

{
  "batch": false,
  "name": "foobar"
}
{
  "batch": true,
  "names": ["foo", "bar", "baz"]
}

With the current implementation of Joi is it possible to make names required only if batch is true and not allowed if batch is false? Reading the docs I think no? But I'm asking because I might be missing something! :)

If not is it something that might be added to Joi or should I do that check "post validation"?

@hueniverse
Copy link
Contributor

Can you suggest an API for this feature?

@HughePaul
Copy link

how about:

withvalue(value,[fieldnames])

with something like:

internals.Any.prototype.withvalue = function (testValue) {

    var args = Array.prototype.slice.call(arguments);
    var peers = Utils.flatten(args.slice(1));

    for (var i = 0, li = peers.length; i < li; i++) {
        Utils.assert(typeof peers[i] === 'string', 'peers must be a string');
    }

    this._test('withvalue', args, function (value, state, options) {
        if(testValue !== value) {
            return null;
        }

        if (!state.parent) {
            return internals.Any.error('any.with.parent', null, state);
        }

        for (var i = 0, il = peers.length; i < il; ++i) {
            if (!state.parent.hasOwnProperty(peers[i]) ||
                state.parent[peers[i]] === undefined ||
                state.parent[peers[i]] === null ||
                state.parent[peers[i]] === '') {

                return internals.Any.error('any.with.peer', { value: peers[i] }, state);
            }
        }

        return null;
    });

    return this;
};

@hueniverse
Copy link
Contributor

@HughePaul looks pretty limited for a very narrow use case. I'm looking for something more generic where you can express any condition based on this or other key values.

@HughePaul
Copy link

perhaps a requiredif([conditions]) way of looking at it?
It would need to be run after all other fields had been validated and converted.
perhaps:

title: Joi.string().required().valid('Mr','Mrs','Ms','Other'),
otherTitle: Joi.string().requiredif( {'title': Joi.string().valid('Other')} )

@hueniverse
Copy link
Contributor

I am looking for something more along the lines of string().condition(some_condition, morerules()) but without needing to parse the condition.

@simme
Copy link
Author

simme commented Feb 2, 2014

The first thing I tried was passing an object to any.with() as in:

{
  names: Joi.array().with({ batch: true })
}

That didn't work obviously. But to me that is the most obvious from an API usage perspective. I'm not familiar enough with the code itself to know wether or not that would be possible.

@hueniverse
Copy link
Contributor

Again, that's a very narrow way of doing conditions.

@simme
Copy link
Author

simme commented Feb 3, 2014

I agree. I'm not sure I understand what you mean by not having to parse the condition though. Care to explain?

@HughePaul
Copy link

Do you mean that the condition would be a function, like:

var schema = Joi.object({
  a: Joi.string()
    .condition(function(){ return this.a === 'canHasB'; }, Joi.any().with('b'))
    .condition(function(){ return this.a === 'canHasC'; }, Joi.any().with('c')),
  b: Joi.number(),
  c: Joi.number()
});

or (if, then) validation parameters

var schema = Joi.object({
  a: Joi.string()
    .condition(Joi.string().valid('canHasB'), Joi.any().with('b')),
    .condition(Joi.string().valid('canHasC'), Joi.any().with('c')),
  b: Joi.number(),
  c: Joi.number()
});

of .if().then() promise style:

var schema = Joi.object({
  a: Joi.string()
    .if(Joi.string().valid('canHasB')).then(Joi.any().with('b'))
// or:
    .if().valid('canHasC').then().with('c')
    .if().valid('canHasBandC').then().with('b').with('c'),
  b: Joi.number(),
  c: Joi.number()
});

or perhaps chains that can fail early, but that might be difficult to work out what is a condition fail and what is an error to report:

var schema = Joi.object({
  a: Joi.string().conditional(
    Joi.string().valid('canHasB').with('b'),
    Joi.string().valid('canHasC').with('c')
  ),
  b: Joi.number(),
  c: Joi.number()
});

Obviously there could be complex validation and complex requirements specified in those sort of formats, but I think there is also still milage in a simple .withIfValue('value','requiredField'...) format as that may cover a lot of conditional requirements I've come across building APIs.

@HughePaul
Copy link

I see the .if().then() format as the prettiest of these, and I could see it implemented as follows:

  • if() would create a new basetype, flag it as an 'if' chain, set a reference to the original chain, and add it to an array of conditionals on the original chain. The new chain would be returned.
  • When a then() is encountered, a new base type is created, flagged as a 'then' with reference to the original chain. This 'then' chain would be filed against the last 'if' chain on the original, i.e. conditionals = [{if: ifChain, then: thenChain}]. The new chain would be returned.
  • When an if() or then() is encountered on an 'if' or 'then' chain, these are run on the original chain.

To validate these clauses the value would be validated against each 'if' chain, and if it didn't error then the value would also be validated against the 'then' chain. (with object() parents set up etc)

does this make any sense?

@HughePaul
Copy link

maybe if could take an optional field name to let you reference a value of another object property, so for the OP's issue the solution could be:

{
  batch: Joi.boolean().required(),
  name: Joi.any().required()
    .if('batch').valid(true).then(Joi.array().includes(Joi.string()).min(1))
    .if('batch').valid(false).then(Joi.string()))
}

So the definitions of if() and then() could be:

if([field_name],[joi_rule_chain]) returns joi_rule_chain or new base type rule chain
then([joi_rule_chain]) returns joi_rule_chain or new base type rule chain

@hueniverse
Copy link
Contributor

Replaced by #194

@hueniverse hueniverse added this to the 2.8.0 milestone Mar 9, 2014
@hueniverse hueniverse self-assigned this Mar 9, 2014
@hueniverse hueniverse added feature New functionality or improvement and removed request labels Sep 19, 2019
@lock
Copy link

lock bot commented Jan 9, 2020

This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature New functionality or improvement
Projects
None yet
Development

No branches or pull requests

3 participants