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

alternatives().match("one") incorrectly reports that there's no match, when the error is that all schemas match #3058

Open
gh-andre opened this issue Dec 22, 2024 · 0 comments
Labels
bug Bug or defect

Comments

@gh-andre
Copy link

Runtime

Node.js

Runtime version

v20.16.0

Module version

[email protected]

Last module version without issue

No response

Used with

Express

Any other relevant information

In addition to what's described below, as an experiment, I also tried match("all") and it isn't working either, failing with alternatives.any type, but if either a or b is commented out, it will correctly report this as an error with type set as alternatives.all. Oddly enough, match("any") is not working with both alternatives present either.

It seems to me that match("one") and match("any") were implemented as if it's the same, although I didn't look at the code, and just inferred it from some of the discussions, like this one:

#2051 (comment)

The functionality of alternatives().match() is similar to oneOf, anyOf and allOf in JSON Schema, so you can see how errors are reported. For example, for this oneOf schema:

https://www.jsonschemavalidator.net/

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$defs": {
    "x": {"type": "string"},
    "y": {"type": "string"}
  },
  "oneOf": [
    {
      "properties": {
        "a": {"type": "number"},
        "x": {"$ref": "#/$defs/x"},
        "y": {"$ref": "#/$defs/y"}
      }
    },
    {
      "properties": {
        "b": {"type": "string"},
        "x": {"$ref": "#/$defs/x"},
        "y": {"$ref": "#/$defs/y"}
      }
    }
  ]
}

, and this object:

{
  "a": 123,
  "b": "123",
  "x": "x-value"
}

, the error is reported as:

JSON is valid against more than one schema from 'oneOf'. Valid schema indexes: 0, 1.

You can change the schema to allOf or anyOf and see the effects, such as allOf will report the index of the failing schema and the individual fields (e.g. if a is changed to "123"), which would be quite useful in Joi.

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

I'm trying to validate a set of input values, some of which are mutually exclusive. For example, a user may identify themselves by an email address or a social media handle, but not both. In addition to the mutually exclusive fields, I also have common fields that should be present in both cases.

In this example, o represents shared fields, a is an email field and b is a plain string field. I would like either a or b to be valid, but not both, while having the same rules for x and y.

let Joi = require("joi");

let o = Joi.object(
    {
        x: Joi.string().min(1).max(30).label("X"),
        y: Joi.string().optional().min(1).max(60).label("Y"),
    });

let x = Joi.alternatives()
    .match("one")
    .try(
        Joi.object({a: Joi.string().email().label("A")}).concat(o),
        Joi.object({b: Joi.string().label("B")}).concat(o)
    )
    .label("AB.XY");

console.dir(x.validate({
    a: "[email protected]",
    b: "b-value",
    x: "x-value"
}), { depth: null });

What was the result you got?

The code above returns the following response, in which it identifies that neither a nor b is allowed, when the actual problem is that they both match, so for the lack of a better terminology they are both "allowed", when only one should be. It also seems incorrect that alternatives.any is reported as type, when the failing matching mode is alternatives.one.

{
  value: undefined,
  error: [Error [ValidationError]: "AB.XY" does not match any of the allowed types] {
    _original: { a: '[email protected]', b: 'b-value', x: 'x-value' },
    details: [
      {
        message: '"AB.XY" does not match any of the allowed types',
        path: [],
        type: 'alternatives.any',
        context: {
          details: [
            {
              message: '"b" is not allowed',
              details: [
                {
                  message: '"b" is not allowed',
                  path: [ 'b' ],
                  type: 'object.unknown',
                  context: {
                    child: 'b',
                    label: 'b',
                    value: 'b-value',
                    key: 'b'
                  }
                }
              ]
            },
            {
              message: '"a" is not allowed',
              details: [
                {
                  message: '"a" is not allowed',
                  path: [ 'a' ],
                  type: 'object.unknown',
                  context: {
                    child: 'a',
                    label: 'a',
                    value: '[email protected]',
                    key: 'a'
                  }
                }
              ]
            }
          ],
          label: 'AB.XY',
          value: { a: '[email protected]', b: 'b-value', x: 'x-value' }
        }
      }
    ]
  }
}

What result did you expect?

match("one") is described that it should match only one schema, same as oneOf in JSON Schema, so it should be reported that more than one schema matched for alternatives().match("one"). Specifically, errors resulting from combining schemas and errors resulting from fields failing validation should be reported independently.

match("one") does report an error, but the error is not reported as more than one schema matching, but rather as neither of the allowed schemas matched. This error does not reflect what actually was invalid and cannot be reported back to the user, so the calling code must detect the errors of combining schemas, discard them and report new ones, while trying to pluck things like labels from the reported errors. I also don't see a way to supply a custom error here, probably because of the concat used for shared fields.

@gh-andre gh-andre added the bug Bug or defect label Dec 22, 2024
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

No branches or pull requests

1 participant