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

Joi.object().xor() throws with "must be a string or a reference" when passed a reference #2471

Closed
cdhowie opened this issue Sep 23, 2020 · 6 comments · Fixed by #2532
Closed
Assignees
Labels
bug Bug or defect
Milestone

Comments

@cdhowie
Copy link

cdhowie commented Sep 23, 2020

Support plan

  • is this issue currently blocking your project? (yes/no): no
  • is this issue affecting a production system? (yes/no): no

Context

  • node version: 12.18.3
  • module version with issue: 17.2.1
  • last module version without issue: Unknown, have not yet tried prior versions
  • environment (e.g. node, browser, native): Node
  • used with (e.g. hapi application, another framework, standalone, ...): Standalone
  • any other relevant information: N/A

What are you trying to achieve or the steps to reproduce?

I am trying to make a schema that will validate an object with any key (Joi.object().pattern(/./, valueSchema)) but where a specific key is required, but which key comes from the object being validated. For example:

const schema = Joi.object().keys({
  defaultTag: Joi.string().required(),
  tags: Joi.object().required().pattern(/./, Joi.object().required()),
});

However, requiring value.tags[value.defaultTag] to be present. There doesn't seem to be a built-in way to accomplish this; I can't find a way to use a ref as an object key. (An API like Joi.object().key(Joi.ref('foo'), Joi.number().required()), for example, would permit this, but I can't find anything like this. Anyway, this is beside the point.)

The simplest approach seemed to be to (ab)use .xor() by giving it a single ref (Joi.ref('defaultTag')). However, the .xor() call results in a confusing/contradictory exception. It boils down to this:

Welcome to Node.js v12.18.3.
Type ".help" for more information.
> const Joi = require('joi');
undefined
> Joi.object().xor(Joi.ref('foo'));
Uncaught Error: xor peers must be a string or a reference

What was the result you got?

Uncaught Error: xor peers must be a string or a reference

What result did you expect?

Either .xor() should accept a reference, or the error message should not indicate that a reference is acceptable.

@brianle1301
Copy link
Contributor

brianle1301 commented Dec 28, 2020

@cdhowie Well spotted! I will label it as a bug for now but the impact of it is low.
Edit: .and(), .or(), .xor(), .oxor(), .with(), nand() all accept strings but not references. So in your case just pass Joi.object().xor('foo')

@cdhowie
Copy link
Author

cdhowie commented Jan 3, 2021

Edit: .and(), .or(), .xor(), .oxor(), .with(), nand() all accept strings but not references. So in your case just pass Joi.object().xor('foo')

I think you misunderstand what I'm trying to accomplish with this code. Joi.object().xor('defaultTag') would require that the defaultTag attribute is set, but that is not what I am trying to do. The validated value should have a string in the defaultTag attribute, and its value is what I want to use for the attribute name.

In other words, the attribute name that is required comes from the validated object, not the schema, but it comes from a known key. That is why I was trying to use Joi.ref(): to say "the input object contains the attribute name that I would like to require."

#2532 implies that this is simply not supported -- which is fine. If this is a feature that would be considered for inclusion, I can open a new issue as a feature request. Currently a two-phase approach is required where the value of defaultTag is first validated, then a final schema is built using this value for a second phase of validation.

@brianle1301
Copy link
Contributor

@cdhowie Sorry I'm a bit confused on what the input data should look like. Can you please provide some inputs of what should pass, what shouldn't and expected outputs?

@brianle1301
Copy link
Contributor

brianle1301 commented Jan 3, 2021

@cdhowie Given the information, I'd assume you want the string that defaultTag holds to be present as a key of tags. This is easy and there is no need to use .xor():

const Joi = require('./lib');

const child = Joi.object().required();
const matches = Joi.array().items(
    Joi.valid(Joi.ref('....defaultTag')).required(),
    Joi.any(),      // Object keys are always string so no need to validate
);

const schema = Joi.object({
    defaultTag: Joi.string().required(),
    tags: Joi.object()
        .pattern(/./, child, { matches })
});

schema.validate({
    defaultTag: 'test',
    tags: { test: {}, a: {} },
}); // Pass

schema.validate({
    defaultTag: 'test',
    tags: { test: {} },
}); // Pass

schema.validate({
    defaultTag: 'test',
    tags: { a: {} },
}); // Fail (missing test)

object.pattern() accepts an optional matches schema that describes what the array of matched keys should look like. In the above snippet, I specified the 2 constraints: A required key defined in defaultTag and anything else (this is for other keys to match against, otherwise the validation will always fail if you provide extra keys).

Hope it solves your issue.

Edit: The pull request is still valid.

@cdhowie
Copy link
Author

cdhowie commented Jan 3, 2021

@cdhowie Given the information, I'd assume you want the string that defaultTag holds to be present as a key of tags. This is easy and there is no need to use .xor():

[snip]

Hope it solves your issue.

Yes, this is perfect. I'd never noticed the matches option for .pattern() and apparently I need to do some reading up on it. Thanks for the pointer.

Edit: The pull request is still valid.

Right, I never meant to imply that the pull request doesn't solve this particular issue. I was just trying to figure out if I needed to open a feature request to accomplish my original goal, and it looks like that's not necessary.

@brianle1301
Copy link
Contributor

@cdhowie So the issue itself is resolved, but the bug it discovers is still unfixed. I will leave it open until my PR is merged.

@hueniverse hueniverse added this to the 17.3.1 milestone Jan 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bug or defect
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants