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

Example of $recursiveRoot and $recursiveRef #605

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions hyper-operations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "http://json-schema.org/draft-08/hyper-operations-FAKE#",
"$id": "http://json-schema.org/draft-08/hyper-operations-FAKE",
"$recursiveRoot": true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This schema extends another recursive schema, and in this PR example, nothing extends it. So it is always the entry point schema when it is used at all.

So here "$recursiveRoot": true pins the target of all "$recursiveRef"s that are encountered.


"title": "FAKE DEMO JSON Hypermedia Operations FOR RECURSION EXAMPLE",
"$ref": "http://json-schema.org/draft-08/hyper-schema",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a regular "$ref", as a "$recursiveRef" here would loop back to this root schema and be an infinite loop. A normal "$ref" is used for the extension reference.

Copy link
Member

@gregsdennis gregsdennis Jun 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we resolve the issue of $ref having sibling keywords? (Otherwise, how does this work?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's an informal decision in #523. I guess you're assuming that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there's also PR #585 which will hopefully start moving again soon. I've been acting as if it's done because that's definitely what draft-08 will look like. (I'm not sure what you mean by "informal decision", do we even have formal decisions here? Other than merging PRs?)

"properties": {
"base": {
"type": "string",
"format": "uri-template"
},
"links": {
"items": {
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"someKindOfSchema": { "$recursiveRef": "#" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "$recursiveRef" is controlled by the "$recursiveRoot": true above, but since (in our example here in the PR), this is a meta-schema that extends others but is never extended, the default target of "#" and the recursive root are the same.

}
}
}
}
}
},
"links": [
{
"rel": "self",
"href": "{+%24id}"
}
]
}
57 changes: 5 additions & 52 deletions hyper-schema.json
Original file line number Diff line number Diff line change
@@ -1,66 +1,19 @@
{
"$schema": "http://json-schema.org/draft-08/hyper-schema#",
"$id": "http://json-schema.org/draft-08/hyper-schema#",
"$id": "http://json-schema.org/draft-08/hyper-schema",
"$recursiveRoot": true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyper-schema is a recursive meta-schema. It extends the core/validation recursive meta-schema, and is in turn extended by the hyper-operations meta-schema (which is fake and made up for this example). It also references links.json, which is the stand-alone LDO schema, which refers back to this meta-schema.

If hyper-schema.json is the entry point schema document, then this "$recursiveRot": true sets the "$recursiveRef" target for all processing. However, if hyper-operations.json is the entry point, then this "$recursiveRoot" is ignored because hyper-operations.json already set it, and we only arrive at this meta-schema through hyper-operations.json.

A more complex example is if links.json is the entry point schema. That is not a recursive schema, so it does not set "$recursiveRoot". However, it does "$recursiveRef" this meta-schema. When such a "$recursiveRef" (or "$ref") is followed from a non-recursive schema, then this "$recursiveRoot" is the first one seen, and sets the target for all further "$recursiveRef"s.


"title": "JSON Hyper-Schema",
"$defs": {
"schemaArray": {
"allOf": [
{ "$ref": "http://json-schema.org/draft-08/schema#/$defs/schemaArray" },
{
"items": { "$ref": "#" }
}
]
}
},
"allOf": [ { "$ref": "http://json-schema.org/draft-08/schema#" } ],
"$ref": "http://json-schema.org/draft-08/schema",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with hyper-operations.json referencing this schema, we use "$ref" to extend the regular core/validation schema.

"properties": {
"additionalItems": { "$ref": "#" },
"additionalProperties": { "$ref": "#"},
"dependencies": {
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "type": "array" }
]
}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/$defs/schemaArray" }
]
},
"$defs": {
"additionalProperties": { "$ref": "#" }
},
"definitions": {
"$comment": "Renamed to $defs, but retained here to ensure compatibility",
"additionalProperties": { "$ref": "#" }
},
"patternProperties": {
"additionalProperties": { "$ref": "#" }
},
"properties": {
"additionalProperties": { "$ref": "#" }
},
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$ref": "#" },
"contains": { "$ref": "#" },
"propertyNames": { "$ref": "#" },

"base": {
"type": "string",
"format": "uri-template"
},
"links": {
"type": "array",
"items": {
"$ref": "http://json-schema.org/draft-08/links#"
"$ref": "http://json-schema.org/draft-08/links"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reference to links.json is a regular "$ref" as it is not a recursive reference to start with.

However, since at this point we have already set "$recursiveRoot": true (either here or from hyper-operations.json), when we see "$recursiveRef" inside of links.json from here, those "$recursiveRef"s have their targets changed to point to the appropriate "$recursive Root".

}
}
},
Expand Down
10 changes: 5 additions & 5 deletions links.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-08/hyper-schema#",
"$id": "http://json-schema.org/draft-08/links#",
"$id": "http://json-schema.org/draft-08/links",
"title": "Link Description Object",
"allOf": [
{ "required": [ "rel", "href" ] },
Expand Down Expand Up @@ -29,7 +29,7 @@
"format": "uri-template"
},
"hrefSchema": {
"$ref": "http://json-schema.org/draft-08/hyper-schema#"
"$recursiveRef": "http://json-schema.org/draft-08/hyper-schema"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using links.json as a standalone schema, it is the entry point schema, and for the initial evaluation it does not matter whether this is a "$ref" or a "$recursiveRef". This schema does not set "$recursiveRoot", so the literal URI reference value provided here is used as-is.

When links.json is referenced from hyper-schema.json (with hyper-schema.json as the entry point), then we have seen as "$recursiveRoot", so the literal value is ignored and the target is the root schema of hyper-schema.json

When links.json is referenced from hyper-schema.json, when the entry point was hyper-operations.json, then the "$recusiveRoot" in hyper-operations.json is the first encountered, so the target of this (and other) "$recursiveRef"s is the root schema of hyper-operations.json

These behaviors show how the same links.json can be used on its own (referring to hyper-schema.json which extends schema.json) and as a component within hyper-schema.json (or an extension of it, such as hyper-operations.json).

},
"templatePointers": {
"type": "object",
Expand All @@ -55,21 +55,21 @@
"type": "string"
},
"targetSchema": {
"$ref": "http://json-schema.org/draft-08/hyper-schema#"
"$recursiveRef": "http://json-schema.org/draft-08/hyper-schema"
Copy link
Contributor Author

@handrews handrews Jun 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no "$recursiveRoot" in links.json when used as an entry point, each time we reference a document with a "$recursiveRoot", the root is set just for that reference and any further references it makes. Then when we move "out" of this "$recursiveReference" and on to the next one for some other LDO property, the process repeats.

This still works fine when we start from links.json, "$recursiveRef" into hyper-schema, and then reference back to links.json again. The sequence is:

  • Enter links.json (no "$recursiveRoot")
  • "$recursiveRef" to hyper-schema (use the URI reference directly as there's no root yet)
    • Enter hyper-schema.json, see "$recursiveRoot" for the first time, set the recursion target
    • "$ref" to schema.json (extending the core/validation meta-schema)
      • Enter schema.json, which does not need "$recursiveRoot" (see 2nd review, I changed this from the initial version)
      • Any "$recursiveRef"s encountered, such as for the applicator keywords, go to the recursion target, which is hyper-schema.json
    • Back out in hyper-schema.json, "$ref" to links (this is never recursive)
      • Re-enter links.json, the recursive root is still set from hyper-schema.json
      • Encounter a "$recursiveRef" to hyper-schema, but now with the recursion target set use that (in this case, it happens to point to the same place)
      • This continues as we walk over the instance and bounce back and forth between links.json, hyper-schema.json, and schema.json

},
"targetMediaType": {
"type": "string"
},
"targetHints": { },
"headerSchema": {
"$ref": "http://json-schema.org/draft-08/hyper-schema#"
"$recursiveRef": "http://json-schema.org/draft-08/hyper-schema"
},
"submissionMediaType": {
"type": "string",
"default": "application/json"
},
"submissionSchema": {
"$ref": "http://json-schema.org/draft-08/hyper-schema#"
"$recursiveRef": "http://json-schema.org/draft-08/hyper-schema"
},
"$comment": {
"type": "string"
Expand Down
43 changes: 25 additions & 18 deletions schema.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"$schema": "http://json-schema.org/draft-08/schema#",
"$id": "http://json-schema.org/draft-08/schema#",
"$recursiveRoot": true,

"title": "Core schema meta-schema",
"$defs": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
"items": { "$recursiveRef": "#" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema.json does not need "$recursiveRoot": true, as it is not extending anything. So using the literal URI reference of # in "$recursiveRef": "#" produces the correct behavior when using schema.json directly.

When used from hyper-schema.json (with either it, links.json, or hyper-operations.json as the entry schema document), a "$recursiveRoot" will be set, so it is important that these references are "$recursiveRef" rather than "$ref".

},
"nonNegativeInteger": {
"type": "integer",
Expand Down Expand Up @@ -55,13 +57,13 @@
},
"$defs": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"default": {}
},
"definitions": {
"$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.",
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"default": {}
},
"title": {
Expand Down Expand Up @@ -101,10 +103,10 @@
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"additionalItems": { "$recursiveRef": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$recursiveRef": "#" },
{ "$ref": "#/$defs/schemaArray" }
],
"default": true
Expand All @@ -115,32 +117,37 @@
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"contains": { "$recursiveRef": "#" },
"maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
"minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/$defs/stringArray" },
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"additionalProperties": { "$recursiveRef": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/$defs/stringArray" }
]
"$recursiveRef": "#"
}
},
"propertyNames": { "$ref": "#" },
"requiredDependencies": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/stringArray"
}
},
"propertyNames": {
"additionalProperties": { "$recursiveRef": "#" }
},
"const": true,
"enum": {
"type": "array",
Expand All @@ -162,13 +169,13 @@
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"if": { "$recursiveRef": "#" },
"then": { "$recursiveRef": "#" },
"else": { "$recursiveRef": "#" },
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$ref": "#" }
"not": { "$recursiveRef": "#" }
},
"default": true
}