-
-
Notifications
You must be signed in to change notification settings - Fork 215
test $recursiveRef + $recursiveAnchor #391
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
Conversation
lgtm 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are great tests. May I suggest two more types:
- Same but with $recursiveAnchor set to false, and
- Same but with an absent $recursiveAnchor, to test that the behaviour of $recursiveRef does indeed follow that of $ref.
"$defs": { | ||
"myobject": { | ||
"$id": "myobject.json", | ||
"$recursiveAnchor": true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, if this "$recursiveAnchor" were removed, would this schema still validate the same way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case yes, because there is an $id at that subschema that sets a new absolute uri, so #
would resolve to the same position.
These are pretty basic tests, and definitely could be expanded upon. These also only involve a single document, without bringing in extra documents from the 'remotes' folder, and an implementation could potentially behave differently in that case. |
$recursiveAnchor=false isn't a schema violation. I'd still like to see this case added. (And for reference, I'm the one who brought up the fact that meta-schema tests should be treated differently in the first place at the end of April, no need to explain that; here's an issue recently created to discuss the topic: #397) |
Here's a spec quote describing $recursiveAnchor (from https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.8.2.4.2.2):
|
https://github.com/json-schema-org/json-schema-spec/blob/master/meta/core.json#L34-L38 |
Interesting. That looks like an error in the schema because it disagrees with the spec. I’ll submit a PR to fix that in the schema. |
Seems to work for me. Is something stalling this? Without this PR, there are almost no tests for |
agreed. I added a test with $recursiveAnchor=false. |
I also added another test case which tripped up my implementation (!!) -- where there is a $recursiveAnchor in the current dynamic scope, but it is not present at the initial target schema. |
Note that this will fail schema processing, per your comment (#391 (comment)), until this PR goes through: json-schema-org/json-schema-spec#945 (for future readers of this PR needing context if they see a failure for these tests). |
This seems approved right? Any reason it hasn't been merged? |
Took a second look, the updated tests don't pass in my impl, investigating. |
Seems to be valid, but perhaps a variant of i.e.: "schema": {
"$id": "http://localhost:4242/recursiveRef7/base.json",
"anyOf": [
{ "type": "boolean" },
{
"type": "object",
"additionalProperties": {
"$id": "http://localhost:4242/recursiveRef7/inner.json",
"$recursiveAnchor": true,
"anyOf": [
{ "type": "integer" },
{ "type": "object", "additionalProperties": { "$recursiveRef": "#" } }
]
}
}
]
}, |
I don't think this is correct. My understanding is that I re-read the spec and there are parts that do make it sound like @handrews, would you mind chiming in and letting us know you intended |
@jdesrosiers is correct.
The "yes+no" case is actually a degenerate form of the "yes+yes" case, but in practical terms it has the same effect as the "no+(no||yes)" case. |
We treat it a mistake to not have a $recursiveAnchor: true in the schema that uses $recursiveRef, so that throws in non-lax modes. Refs: json-schema-org/JSON-Schema-Test-Suite#391
Is this good to merge as-is, and work on additional tests in a separate PR? |
Sounds good to me too! |
] | ||
}, | ||
{ | ||
"description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is wrong as discussed in previous comments. Let's fix or remove this test, then I would love to see this merged!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdesrosiers Could you help me out by pointing to which tests are incorrect? I do not see any that are wrong. (revisions for $dynamicRef do not apply to this version of the spec.)
specifically, regarding recursiveRef6, there is no $recursiveAnchor: true at the initial $recursiveRef target, so it behaves like a regular $ref, and we only recurse to the inner subschema, and never the outer one. For the schemas that are used in these tests (and the initial target is '#'), we need to have $recursiveAnchor: true in both the base and inner subschemas in order to recurse to the base -- if either are missing, then we revert to normal $ref behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to have $recursiveAnchor: true in both the base and inner subschemas in order to recurse to the base
That's not correct. That's what I was pointing out in #391 (comment) (and Henry confirmed was the correct interpretation). $recursiveRef
should use the first $recursiveAnchor
in the heirarchy that was set. $recursiveAnchor
is not required to be in every schema $recursiveRef
appears in.
So, the second and third tests are wrong as well as the $comment
.
(If this needs more discussion, I suggest removing these tests for now and getting the rest merged. We can add these back separately when everyone is in agreement.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so we're talking about the test schemas with ids http://localhost:4242/recursiveRef2/schema.json
and http://localhost:4242/recursiveRef3/schema.json
? I need to be sure if I'm going to merge "the rest".
$recursiveAnchor is not required to be in every schema $recursiveRef appears in.
I don't see any tests that assert that in comments, nor assume that in their schemas. While all the $recursiveRef targets in these tests are '#', that resolves to a different position than the schema $recursiveRef appears in (because of the use of allOf). What is required is that the initial $recursiveRef target have a $recursiveAnchor: true in order to consider earlier/outer $recursiveAnchors, and all the tests are properly relying on that.
Please could you be specific about what schemas and subtests are not behaving properly? This is important not just to get the tests right, but to fix all the implementations that are assuming the tests are right and presently validate against these tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we're talking about the group of tests that this comment is on. The one with "description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource",
. The one with http://localhost:4242/recursiveRef6/inner.json
.
I'll comment on the specific lines in question to make sure it can't be missed, but I'd suggest leaving out this whole group of tests for now as the premise of the test is wrong.
…(again reverts to normal $ref behaviour)
] | ||
}, | ||
{ | ||
"description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we're talking about the group of tests that this comment is on. The one with "description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource",
. The one with http://localhost:4242/recursiveRef6/inner.json
.
I'll comment on the specific lines in question to make sure it can't be missed, but I'd suggest leaving out this whole group of tests for now as the premise of the test is wrong.
"type": "object", | ||
"additionalProperties": { | ||
"$id": "http://localhost:4242/recursiveRef6/inner.json", | ||
"$comment": "there is no $recursiveAnchor: true here, so we do NOT recurse to the base", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This $comment
is wrong. It doesn't matter that there isn't a $recursiveAnchor
in the embedded schema. The $recursiveAnchor
that is set on line 239 still applies to this schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is the comment wrong? The initial target of the $recursiveRef is here: '#' resolves to inner.json. The rules for $recursiveRef explicitly says ""$recursiveRef" behaves identically to "$ref", except when its target schema contains "$recursiveAnchor" with a value of true." So the initial target is the final target - we recurse to inner.json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@handrews, we were hoping you could help us resolve this. I previously commented on this PR the following.
My understanding is that
$recursiveAnchor
is not intended to be a flag to enable or disable$recursiveRef
functionality. It marks a schema as being allowed to be the target of a$recursiveRef
. A$recursiveRef
only behaves like$ref
if there is no$recursiveAnchor
anywhere in it's dynamic scope. Not every schema in the chain needs to have a$recursiveAnchor
.
You indicated that that was correct.
However, after talking with @karenetheridge and re-reading the spec, it does sound like the spec says that a $recursiveRef
can only consider the dynamic scope if there is a $recursiveAnchor
in it's lexical scope.
If you could have a look at these tests and tell us how you expect them to validate, I think that should clear up what the intended behavior was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdesrosiers $recursiveRef
has three steps IIRC
- evaluate as if
$ref
to find the preliminary target schema - if the preliminary target schema has
$recursiveAnchor: true
, proceed to the next step, otherwise the preliminary target schema is the final target schema and the reference is considered resolved - unwind the dynamic scope to find the outermost
$recursiveAnchor: true
, regardless of whether intermediate dynamic scopes also have$recursiveAnchor: true
; this is the intermediate target schema - find the base URI of the intermediate target schema, and re-resolve the value of
$recursiveRef
against that base URI - this produces the final target schema, and the reference is now resolved. (although I think we limited the value of$recursiveAnchor
to#
or at least said it always had to end in an empty fragment, so in practice the final target schema is always the same as the intermediate target schema)
Keep in mind that $recursive*
have cases that don't work, notably as shown in links.json
so there's no sensible complete test suite that both strictly conforms to the spec and works with links.json
. I forget the details but the keywords are at least a little broken, and I'm really hoping no one actually uses 2019-09 and had advised only providing a test suite for 2020-NN in order to encourage that. But I guess we're supporting 2019-09 so 🤷 just be aware $recursive*
doesn't entirely make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great, so given this, it sounds like all the test cases in this PR are correct as written (note I made a few amendments that will be squashed together in the merge commit). @jdesrosiers do you agree?
(Note: as I said on slack#tests, I think it's perfectly acceptable to acknowledge that $recursiveRef isn't quite right in this draft, so one shouldn't try to do anything too complicated with it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @handrews. That's what I got from re-reading the spec.
unwind the dynamic scope to find the outermost $recursiveAnchor: true, regardless of whether intermediate dynamic scopes also have $recursiveAnchor: true; this is the intermediate target schema
This is where our lines got crossed. We agreed on this part, but I was missing the part where we only do this step if we have $recursiveAnchor
in the same schema as the $recursiveRef
.
@karenetheridge these tests don't yet cover this case where we have an intermediate dynamic scope that doesn't have a $recursiveAnchor
. It can be added later, but I wanted to point that out.
As far as I can tell, the two step resolve and $recursiveAnchor
enabling/disabling $recursiveRef
seem unnecessary. I can't tell why it doesn't just resolve the $recursiveRef
against the current dynamic scope and be done. But, $dynamicAnchor
/$dynamicRef
handled this way better, so it doesn't matter any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but I was missing the part where we only do this step if we have
$recursiveAnchor
in the same schema as the$recursiveRef
.
Correction: in the initial target schema. Not in the schema containing the $recursiveRef
(which is the context or source or referring schema I don't remember what I called it)
"description": "leaf node matches: recursion uses the inner schema", | ||
"data": { "foo": { "bar": 1 } }, | ||
"valid": true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be valid=false. Recursion uses the outer schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree, as per @handrews's latest comment.
"description": "leaf node matches: recursion uses the inner schema", | ||
"data": { "foo": { "bar": true } }, | ||
"valid": false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be valid=true. Recursion uses the outer schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree, as per @handrews's latest comment.
{ | ||
"description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource", | ||
"schema": { | ||
"$id": "http://localhost:4242/recursiveRef6/base.json", | ||
"$recursiveAnchor": true, | ||
"anyOf": [ | ||
{ "type": "boolean" }, | ||
{ | ||
"type": "object", | ||
"additionalProperties": { | ||
"$id": "http://localhost:4242/recursiveRef6/inner.json", | ||
"$comment": "there is no $recursiveAnchor: true here, so we do NOT recurse to the base", | ||
"anyOf": [ | ||
{ "type": "integer" }, | ||
{ "type": "object", "additionalProperties": { "$recursiveRef": "#" } } | ||
] | ||
} | ||
} | ||
] | ||
}, | ||
"tests": [ | ||
{ | ||
"description": "leaf node does not match; no recursion", | ||
"data": { "foo": true }, | ||
"valid": false | ||
}, | ||
{ | ||
"description": "leaf node matches: recursion uses the inner schema", | ||
"data": { "foo": { "bar": 1 } }, | ||
"valid": true | ||
}, | ||
{ | ||
"description": "leaf node matches: recursion uses the inner schema", | ||
"data": { "foo": { "bar": true } }, | ||
"valid": false | ||
} | ||
] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the lines that should be left out for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@karenetheridge It seems Henry doesn't have anything to say about the issue in question. Therefore, I'm going to assume the most likely scenario, which is that Henry and I misunderstood each other. I withdraw my objections. I'm happy with merging these tests as they are.
@jdesrosiers I just missed your earlier comment, let me check... |
Ok I'm going to rebase, squash and merge this tomorrow if I don't hear howls of anguish. |
test $recursiveRef + $recursiveAnchor
rebased, squashed and merged. |
Looks like merging this has started failing the CI build. Not sure if that was as part of the merge or otherwise but if possible can you have a look? |
Looks like a duplicate description. |
I'll fix. |
* ether/unrecognized-keywords: Move the future keywords tests to optional. squash: add $schema keyword to be clear that strict conformance is requested squash: contains does not exist in draft4 squash: fix minContains sq - add $dynamic*, prefixItems to drafts 4,6,7 as well and fix keyword name squash: fix test names squash: $defs - definitions squash: remove tests of $anchor and $ref squash: add prefixItems: another new 2020-0X keyword squash: add $recursiveRef tests from #391 squash - fix 2019-09 future-keywords very basic test of $dynamicRef rename files to future-keywords.json some tests for keywords added in later drafts
No description provided.