santhosh-tekuri / jsonschema

JSONSchema (draft 2020-12, draft 2019-09, draft-7, draft-6, draft-4) Validation using Go
Apache License 2.0
957 stars 98 forks source link

Array of oneOfs with required fields fails validation #105

Closed byrnedo closed 1 year ago

byrnedo commented 1 year ago

Hi!

We're trying to migrate to your library instead of https://github.com/xeipuuv/gojsonschema which we currently use and came across the following that we didn't expect to fail:

If you have an array referring to oneOfs that have a const discriminator and required fields, the validation will fail.

Here's a minimal reproduction:

func TestMinimalRepro(t *testing.T) {
    sch, err := jsonschema.CompileString("https://foo.com/test", `{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://foo.com/test/definitions.json",
  "properties": {
    "foobar": {
      "type": [
        "array",
        "null"
      ],
      "default": null,
      "items": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "first": {
                "type": "string",
                "const": "A"
              },
              "second": {
                "type": "boolean"
              }
            },
            "required": [
              "first",
              "second"
            ]
          },
          {
            "type": "object",
            "properties": {
              "first": {
                "type": "string",
                "const": "B"
              }
            },
            "required": [
              "first"
            ]
          },
          {
            "type": "object",
            "properties": {
              "first": {
                "type": "string",
                "const": "C"
              }
            },
            "required": [
              "first"
            ]
          }
        ]
      }
    }
  }
}
`)
    if err != nil {
        t.Fatal(err)
    }
    if err := sch.Validate(map[string]interface{}{
        "foobar": []interface{}{
            map[string]interface{}{"first": "A", "second": false},
            map[string]interface{}{"first": "B"},
        },
    }); err != nil {
        t.Fatal(err)
    }
}

Error is:

jsonschema: '/foobar/1' does not validate with https://foo.com/test/definitions.json#/properties/foobar/items/oneOf/0/required: missing properties: 'second'
santhosh-tekuri commented 1 year ago
{
    "foobar": [
        {
            "first": "A",
            "second": true
        },
        {
            "first": "X"
        }
    ]
}

the complete error I get when %#v is used:

[I#] [S#] doesn't validate with https://foo.com/test/definitions.json#
  [I#/foobar/1] [S#/properties/foobar/items/oneOf] oneOf failed
    [I#/foobar/1] [S#/properties/foobar/items/oneOf/0]
      [I#/foobar/1] [S#/properties/foobar/items/oneOf/0/required] missing properties: 'second'
      [I#/foobar/1/first] [S#/properties/foobar/items/oneOf/0/properties/first/const] value must be "A"
    [I#/foobar/1/first] [S#/properties/foobar/items/oneOf/1/properties/first/const] value must be "B"
    [I#/foobar/1/first] [S#/properties/foobar/items/oneOf/2/properties/first/const] value must be "C"```

this is the expected result. the instance json you passed does not validate with the provided schema:

let me clarify why it is invalid:

second item {"first": "X" } does not satisfy:

just to clarify: both const and required are independent validations and both must be satisfied.

byrnedo commented 1 year ago

Ah I'm sorry @santhosh-tekuri , that was an incorrect reproduction I gave you to begin with. Should have checked that twice. I'll open a new ticket with a correct reproduction. Thanks for the quick and thorough reply.

santhosh-tekuri commented 1 year ago

if you are ok in sharing your schema and instance, then you can raise issue. no need to struggle for minimal reproduction

byrnedo commented 1 year ago

After actually looking through this the problem is on our side. 🤦 It was the same scenario but on of the oneOf had an extra field that could be type [ "string", "null"] and which was also required, and that was not being sent in the payload, so of course that won't pass validation.