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

Test interaction between unevaluatedProperties and additionalProperties #755

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

V02460
Copy link

@V02460 V02460 commented Nov 20, 2024

I found this scenario where the output of check-jsonschema deviates from my reading of the spec.

In my understanding, unevaluatedProperties: false should evaluate successfully if the subschema additionalProperties matches all properties. E.g. {"foo": "foo"} should be a valid instance of the following schema.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": { "type": "string" },
    "unevaluatedProperties": false
}

@V02460 V02460 requested a review from a team as a code owner November 20, 2024 16:20
@gregsdennis
Copy link
Member

gregsdennis commented Nov 20, 2024

We do have this test already:

{
"description": "unevaluatedProperties with adjacent additionalProperties",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"foo": { "type": "string" }
},
"additionalProperties": true,
"unevaluatedProperties": false
},
"tests": [
{
"description": "with no additional properties",
"data": {
"foo": "foo"
},
"valid": true
},
{
"description": "with additional properties",
"data": {
"foo": "foo",
"bar": "bar"
},
"valid": true
}
]
},

Your case should be covered by the "with additional properties" scenario. Note how bar is accepted by additionalProperties and thereby ignored by unevaluatedProperties.

@V02460
Copy link
Author

V02460 commented Nov 20, 2024

Thanks for taking a look :) I am aware of this test, I actually started writing this one based off it. The difference between the two is that a real life JSON Schema implementation (check-jsonschema) works correctly for one, but not for the other. That is a strong reason to cover both variants in the test suite.

@gregsdennis
Copy link
Member

gregsdennis commented Nov 20, 2024

I'd be interested in what the difference between the tests is that trips up the implementation. To me, your new test is a duplicate. If you can do some experimentation with it to isolate what the trigger is, maybe we can better target the test.

@V02460
Copy link
Author

V02460 commented Nov 20, 2024

I did experiment a bit and to me it seems the difference is whether additionalProperties is false or is an object. Thus the “non-bool” bit in the test’s description.

If this is about the description of the tests, I’m fine with rewording them to, e.g.

  • unevaluatedProperties with adjacent boolean additionalProperties
  • unevaluatedProperties with adjacent object additionalProperties

or anything else you see fit.

I’m not so happy about calling this test a duplicate. Tests do only ever confirm that some property holds for a single input. For everything but the most trivial cases, the wast majority of inputs are never tested. So for any one of these inputs an implementation might misbehave while working fine for the rest. Thus adding a test for another input is not adding a duplicate, but sampling more of the input space and gaining more confidence a property holds.

@V02460 V02460 closed this Nov 20, 2024
@V02460
Copy link
Author

V02460 commented Nov 20, 2024

Sry, reopening, closed by accident

@V02460 V02460 reopened this Nov 20, 2024
@gregsdennis
Copy link
Member

I'll let @Julian weigh in here, but I wonder whether the implementation is failing to handle additionalProperties as an object overall. (We do have tests for that also.)

I'm not arguing that a case that an implementation misses shouldn't be added. In fact, our general policy is to include these cases. I'm just trying to dig down to find what the implementation is actually failing on so that we can create the appropriate test.

@karenetheridge
Copy link
Member

I think the additional test being proposed in this PR is good and we should keep it, and additionally we should simplify the existing test that Greg linked to above -- the type keyword is redundant and should be removed, and also the properties keyword should be removed (or we split this into two tests -- one with properties and one with additionalProperties -- where the outcome is the same for both, that all string properties are evaluated, and therefore "unevaluatedProperties": false doesn't cause invalidity.

It's possible that this failing implementation is looking directly at "additionalProperties": true and short-circuiting on that, or perhaps it is failing to interpret this the same as "properties": {"type": "string"} evaluating to true for every property in the instance. Splitting into two tests will let us see which of these is true -- or something else entirely.

@Julian
Copy link
Member

Julian commented Nov 21, 2024

Thanks for the PR.

We (speaking on behalf of the implementation for a minute) definitely handle additionalProperties fine on its own, so it's something in the interaction between the two we're not doing right, and probably something trivial to fix (I'd bet it's a one line fix without having looked yet) and yes probably has to do with assuming that additionalProperties always is a boolean when it's next to unevaluatedProperties or something trivially wrong like that.

(Back to ignoring the implementation) -- splitting/simplifying the existing test does seem right to me as well, though changing existing tests is careful work, so I'd typically hope if a new contributor is willing to try to figure that out great, if not this new test seems pretty straightforward to me to merge and then file an issue to do that for whoever is willing.

@V02460
Copy link
Author

V02460 commented Nov 23, 2024

Simplified tests by removing the superfluous type keywords in the whole file and additionally removing the properties keyword from the adjacent additionalProperties test. Splitting it up was not necessary, because the adjacent properties test does exactly that.

Is there an easy way to actually run this test suite with one or more tools btw?

@jeremyfiel
Copy link

here's a testing result from @sourcemeta/jsonschema validate --trace command

notice, unevaluatedProperties is never considered.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": { "type": "string" },
    "unevaluatedProperties": false
}
{
  "foo": "foo"
}
-> (push) "/additionalProperties"
   at ""
   at keyword location "#/additionalProperties"

<- (pass) "/additionalProperties"
   at ""
   at keyword location "#/additionalProperties"
{
  "foo": "foo",
  "bar": 1
}
-> (push) "/additionalProperties"
   at ""
   at keyword location "#/additionalProperties"

<- (fail) "/additionalProperties"
   at ""
   at keyword location "#/additionalProperties"

Using the Official JSON Schema Test

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "foo": {
            "type": "string"
        }
    },
    "additionalProperties": true,
    "unevaluatedProperties": false
}
{
  "foo": "foo"
}
-> (push) "/properties/foo/type"
   at "/foo"
   at keyword location "#/properties/foo/type"

<- (pass) "/properties/foo/type"
   at "/foo"
   at keyword location "#/properties/foo/type"

-> (push) "/type"
   at ""
   at keyword location "#/type"

<- (pass) "/type"
   at ""
   at keyword location "#/type"
{
    "foo": "foo",
    "bar": 1
}
-> (push) "/properties/foo/type"
   at "/foo"
   at keyword location "#/properties/foo/type"

<- (pass) "/properties/foo/type"
   at "/foo"
   at keyword location "#/properties/foo/type"

-> (push) "/type"
   at ""
   at keyword location "#/type"

<- (pass) "/type"
   at ""
   at keyword location "#/type"

Interestingly, it doesn't have any output for this schema for either instance. I wonder if it's silently failing. @jviotti

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": true,
    "unevaluatedProperties": false
}
{
  "foo": "foo"
}
{
  "foo": "foo",
  "bar": 1
}

@jviotti
Copy link
Member

jviotti commented Dec 16, 2024

Interestingly, it doesn't have any output for this schema for either instance. I wonder if it's silently failing. @jviotti

Note that latter schema is doing nothing, so no assertions are generated! You are not adding a type, plus saying that if it is an object, everything is valid (additionalProperties: true), and that if it is an object, no unevaluated properties are allowed, which would never happen because of additionalProperties anyway

@V02460
Copy link
Author

V02460 commented Jan 13, 2025

Does this need something to move forward?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants