davishmcclurg / json_schemer

JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1.
MIT License
399 stars 64 forks source link

oneOf is not evaluated correctly #152

Closed pboling closed 11 months ago

pboling commented 11 months ago

oneOf: (XOR) Must be valid against exactly one of the subschemas

ref

I'm still refactoring json-fuzz-generator into json_schemer-fuzz, and I am getting an error in the tests for oneOf:

(byebug) schema
{"$schema"=>"http://json-schema.org/draft-04/schema#", "oneOf"=>[{"properties"=>{"a"=>{"type"=>"string"}}, "required"=>["a"]}, {"properties"=>{"b"=>{"type"=>"integer"}}}]}
(byebug) schemer.valid?({"a" => "hoge"})
false

Here is the schema blown out:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "oneOf": [
    {
      "properties": {
        "a": {
          "type": "string"
        }
      },
      "required": [
        "a"
      ]
    },
    {
      "properties": {
        "b": {
          "type": "integer"
        }
      }
    }
  ]
}

Of note -

davishmcclurg commented 11 months ago

Looking at this quickly, I think { "a": "hoge" } is valid for both oneOf schemas (meaning the oneOf is invalid) because the second schema doesn't specify anything as required (or have any other requirements for properties other than b).

ahx commented 11 months ago

I think @davishmcclurg is correct. { "a": "hoge" } should be valid with a anyOf or allOf, but should fail with oneOf.

pboling commented 11 months ago

Wonderful! Thanks for correcting my thinking! I'll update the tests.

pboling commented 11 months ago

I want to make sure I understand this.

The "b" side matches the {"a": "hoge"} as valid because

By default any additional properties are allowed.

And thus it would match literally anything provided that was a valid schema? So in reality it makes no difference what the "b" schema is, so long as it is not restrictive with required or the like, anything will match it?

I guess I answered my own question. Thanks!

>> JSONSchemer.schema({ "properties": { "b": { "type": "integer" } } }).valid?({"lulz": [1, "what"]})
=> true
davishmcclurg commented 11 months ago

Yes that's right. If I remember correctly, properties doesn't even require the instance to be an object (you'd need "type": "object" for that).

jeremyfiel commented 11 months ago

The best way to describe this is the lack of constraints provided in each schema.

Either one is basically wide open to any schema unless the properties defined are given in the instance data, then those properties would be constrained by the defining assertions. String/required or integer.

One interesting behavior you may not expect with oneOf is without declaring a type for the schema, it remains unconstrained to any other type of valid JSON Schema schema type.

Thus, these are all failing schemas because they pass both schemas

[ { "BBB": true}]
111
{
  "a": "test",
  "c": "blah"
}
true
[]
null

And so on...

If you wanted to constrain this oneOf you can define to as such

{
  "oneOf": [ 
   { "type": "object",
        "properties": {
          "a": { "type": "string"}
        }, "additionalProperties": false
   }, 
   {
      "type": "object",
      "properties": {
          "b": { "type": "integer" }
       }, "additionalProperties": false
  }
 ]
}

Then you can only pass with

{ "a": "test"}

Or

{"b": 123}