-
-
Notifications
You must be signed in to change notification settings - Fork 308
Improve/simplify "$recursiveAnchor" and "$recursiveRef" #909
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
Comments
I am not opposed to this. It would allow multiple recursive paths, which could yield some interesting schema sets. |
The implementation will be a little more complicated, but this should work. The symmetry with |
It occurs to me that these keywords work kind of like |
I'm still catching up with the slack chatter, but I'm not sure I fully understand what the change is here. In your example, if the
If I'm understanding right, this change simply allows for multiple recursive anchor targets. Is this correct? If I am, what's the usecase? I didn't see it here (sorry if I've missed it). I'm not saying there isn't a use case, just I'm not sure what it is right now. If there isn't a clear use case, this seems like adding complexity good reason (Which I don't believe anyone here would do), so I'm going to assume I'm missing something...? |
That's more of an interesting side effect than the main point. I'll come back to the use case question at the end.
Boolean form:
Name form:
The goals are:
There is definitely a use case for named anchors vs only {
"type": "object",
"$recursiveRef": "#subschema",
"properties": {"$schema": {"type": "string", "format": "uri"}},
"$defs": {
"subschema": {
"$recursiveAnchor": "subschema",
"properties": {
"additionalProperties": {"$recursiveRef": "#subschema"},
"not": {"$recursiveRef": "#subschema"},
...
}
}
}
} Now it would be possible to rework this by making it multiple schema resources and using the boolean As for multiple recursive anchors at once, if you have two sets of recursive schemas and you want them to refer to each other, this should work intuitively, as opposed to the boolean form where you'll recurse to whichever one you entered first. Do we need that? Unclear. But I like that it's no longer egregiously broken like it is with the boolean form. |
Wow. Totally sold. I don’t know how the issue was identified, but your response clearly illustrates there’s an issue with an easy solution. People are going to struggle because of “base URI (I still forget this confuses people) is a good reason to make the change. This is already an advanced feature that’s only intended for meta schema authors. If you want to do a minimal PR on this, or at least kick it off, I’m happy to pick it up. I should have some time this week! |
I made this change in my implementation and it seem to work fine. It took a while to figure out exactly what I needed to do, but when I figured it out, the actual code change was small and only took a couple minutes. In fact, it was so small a change that it took me a while to convince myself that it worked and I wasn't missing something. The next issue I'd have to tackle is how to support the 2019-09 version in 2019-09 and the 2020-XX version in 2020-XX. What should happen when a 2020-XX schema extends a 2019-09 schema? If I figure that out, I'll deploy a dialect to https://json-schema.hyperjump.io so people can test it out themselves. |
@jdesrosiers thanks for trying this out! I've been struggling a bit with 2019-09 vs 2020-XX. To the best of my knowledge, 2019-09 isn't substantially deployed anywhere. Until the last couple weeks, there was only one implementation. My ideal approach would actually be to discourage implementing or supporting 2019-09 and asking folks to just update to 2020-XX. (And if they want to support draft-06 and draft-07 that's fine, just skip 2019-09). 2019-09 has served its purpose: it provoked feedback and was a successful proof-of-concept for re-converging with OpenAPI. I don't see any need for it to be a production draft in any sort of ongoing way. |
It's not like 2020-XX will be a radical departure. If you implemented 2019-09, then rolling that forward to 2020-XX should be pretty simple. |
@jdesrosiers @Relequestual @gregsdennis the more I think about this the more That would also mean that if anyone really wanted to keep 2019-09 as an option, it wouldn't conflict anymore. |
I'm ok with ditching 2019-09 and/or changing the name. However, I did figure out a way to support both versions (mostly). If we consider You can try it out on http://json-schema.hyperjump.io by using the cheat code |
@handrews currently (I'm fine with calling it |
Wait, is that true? That's not how I implemented it. The dynamic scope gets passed through every reference whether |
I think @jdesrosiers is right, although there may have been a stage where it was discussed more like @gregsdennis states. Honestly I had to go look it up. This is part of why I want to revamp these: the idea that every point along an extension needs to signal its participation is the logical conclusion of the cooperative model, but then you get into whether an intervening scope is part of the extension chain or some other scope. For example, the recursion for Hyper-Schema bounces through the links schema, potentially switching among the links schema and various recursive meta-schemas many times.
@gregsdennis I seriously considered this which is one reason that the PR is taking so long. After going back and forth, I decided that it was too magical. A schema having a reference with no clear resolution is somewhat confusing- it's hard to tell whether it was intentional or a mistake, although the fact that it's a The idea is that the same So that initial |
[reads code] Yeah, I'm not requiring it at every level. Okay. @handrews and I definitely discussed that though. |
These keywords depend on the concept of the dynamic scope, the runtime schema traversal structure including which references were followed how often to reach the current schema+instance locations.
Note that @awwright has also filed #907 regarding this same problems space. While I don't currently see how that proposal solves the entire use case, I am very much open to being sold on other proposals for this.
Currently,
$recursiveAnchor
takes a boolean which MUST betrue
, and$recursiveRef
takes a URI-reference which MUST either be an empty fragment or an absolute-URI. (Note: The text says it MUST be"#"
, but we actually use absolute-URIs as well so the text is in error.).The mechanism for how these works is described a complex process of evaluating the
$recursiveRef
twice with different base URIs, depending on the presence of$recursiveAnchor
.This is why there's a (incorrectly specified) restriction on what kind of URI-references are legal for
$recursiveRef
. Path references (e.g.foo/bar
) when used with this process could produce very confusing results without any clear use case.It's not actually that important to understand exactly how it works currently. It's probably better to think of this as a new proposal.
We at minimum need to fix the 'MUST be
"#"
' problem, and it would be nice to make this less complex.I propose changing
$recursiveAnchor
so that it takes the same sort of value as$anchor
, and produces a plain name fragment identifier, just like$anchor
.The difference between the two is that when a
$recursiveAnchor
is targeted by a$recursiveRef
, the final resolved target will not necessarily be the locally declared fragment, but the fragment of the same name in the outermost dynamic scope that also declares that fragment name with$recursiveAnchor
. That's a lot of word salad, so we'll go through an example. But first:$recursiveRef
s that do not point to a$recursiveAnchor
act just like$ref
$ref
s that point to a$recursiveAnchor
work just a if it were$anchor
$recursiveRef
targets a$recursiveAnchor
do they behave differently.This ensures that all schemas involved (the schema object making the reference, the local subschema with the plain name fragment, and the final resolved subschema with the plain name fragment) all intentionally opt-in to this behavior. We do not want magical behavior involving
$ref
or$anchor
, ever. Most people will use$ref
and$anchor
(or at least$ref
), so they need to be simple and predictable.$recursiveRef
and$recursiveAnchor
are advanced keywords suitable for a very specific use case.To see how this works, let's look at:
In all cases, I'm omitting
$vocabulary
,title
, and other inessentials to save space, and of course I'm using the new$recursiveAnchor
syntax.Applicator Vocabulary
I'm only including enough applicators here to get the point across
Standard Dialect
When we evaluate just the applicator meta-schema against a schema, there is only one meta-schema resource involved. In this situation,
$recursiveAnchor
and$recursiveRef
behave exactly like$anchor
and$ref
.When we evaluate the standard dialect meta-schema, the
$recursive*
keywords behave differently, as follows:allOf
branch in turn, most notably the one to"meta/applicator"
"$recursiveRef": "#meta"
"#meta"
fragment declaration in the current schema resource, and find it- it is defined by"$recursiveAnchor"
, so we have more steps to take.#meta
fragment with"$recursiveAnchor"
"$recursiveAnchor": "meta"
, and is the outermost scope that does (it's also the only other scope in this example)."$recursiveRef": "#meta"
in the applicator vocabulary is the"#meta"
fragment in the dialect meta-schema.The effect here is that since we started from the dialect schema, which declares a
"$recursiveAnchor": "meta"
, and the applicator vocab schema also declares a"$recursiveAnchor": "meta"
, the"$recursiveRef": "#meta"
in the applicator vocab schema resolves to the anchor in the dialect schema. Same plain name fragment, different resource.The text was updated successfully, but these errors were encountered: