go-sdk/jsonschema/testdata/draft2020-12/dynamicRef.json
Jonathan Amsterdam 2a09582fa4 jsonschema: dynamic references
A dynamic reference is like a dynamic variable in a programming
language, with two twists:

- There must be a dynamic anchor in lexical scope for the ref to
  resolve. (Think of it as declaring the dynamic variable.)

- The ref resolves to the schema highest on the stack, not lowest.
  That feels odd, but it turns out to work for the intended purpose,
  which is to provide extension points for additional features.

There is some distracting code motion and refactoring in this CL:
resolveRefs was moved down in the file, and resolution of a single
ref was split off.

Change-Id: I17da6052e0782fc540d14b8a41b06a2645923872
Reviewed-on: https://go-review.googlesource.com/c/tools/+/674155
Auto-Submit: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-21 05:05:04 -07:00

815 lines
27 KiB
JSON

[
{
"description": "A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
}
}
},
"tests": [
{
"description": "An array of strings is valid",
"data": ["foo", "bar"],
"valid": true
},
{
"description": "An array containing non-strings is invalid",
"data": ["foo", 42],
"valid": false
}
]
},
{
"description": "A $dynamicRef to an $anchor in the same schema resource behaves like a normal $ref to an $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamicRef-anchor-same-schema/root",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"foo": {
"$anchor": "items",
"type": "string"
}
}
},
"tests": [
{
"description": "An array of strings is valid",
"data": ["foo", "bar"],
"valid": true
},
{
"description": "An array containing non-strings is invalid",
"data": ["foo", 42],
"valid": false
}
]
},
{
"description": "A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root",
"type": "array",
"items": { "$ref": "#items" },
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
}
}
},
"tests": [
{
"description": "An array of strings is valid",
"data": ["foo", "bar"],
"valid": true
},
{
"description": "An array containing non-strings is invalid",
"data": ["foo", 42],
"valid": false
}
]
},
{
"description": "A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/typical-dynamic-resolution/root",
"$ref": "list",
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"items": {
"$comment": "This is only needed to satisfy the bookending requirement",
"$dynamicAnchor": "items"
}
}
}
}
},
"tests": [
{
"description": "An array of strings is valid",
"data": ["foo", "bar"],
"valid": true
},
{
"description": "An array containing non-strings is invalid",
"data": ["foo", 42],
"valid": false
}
]
},
{
"description": "A $dynamicRef without anchor in fragment behaves identical to $ref",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamicRef-without-anchor/root",
"$ref": "list",
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#/$defs/items" },
"$defs": {
"items": {
"$comment": "This is only needed to satisfy the bookending requirement",
"$dynamicAnchor": "items",
"type": "number"
}
}
}
}
},
"tests": [
{
"description": "An array of strings is invalid",
"data": ["foo", "bar"],
"valid": false
},
{
"description": "An array of numbers is valid",
"data": [24, 42],
"valid": true
}
]
},
{
"description": "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root",
"$ref": "intermediate-scope",
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
},
"intermediate-scope": {
"$id": "intermediate-scope",
"$ref": "list"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"items": {
"$comment": "This is only needed to satisfy the bookending requirement",
"$dynamicAnchor": "items"
}
}
}
}
},
"tests": [
{
"description": "An array of strings is valid",
"data": ["foo", "bar"],
"valid": true
},
{
"description": "An array containing non-strings is invalid",
"data": ["foo", 42],
"valid": false
}
]
},
{
"description": "An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-resolution-ignores-anchors/root",
"$ref": "list",
"$defs": {
"foo": {
"$anchor": "items",
"type": "string"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"items": {
"$comment": "This is only needed to satisfy the bookending requirement",
"$dynamicAnchor": "items"
}
}
}
}
},
"tests": [
{
"description": "Any array is valid",
"data": ["foo", 42],
"valid": true
}
]
},
{
"description": "A $dynamicRef without a matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-resolution-without-bookend/root",
"$ref": "list",
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"items": {
"$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref",
"$anchor": "items"
}
}
}
}
},
"tests": [
{
"description": "Any array is valid",
"data": ["foo", 42],
"valid": true
}
]
},
{
"description": "A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/unmatched-dynamic-anchor/root",
"$ref": "list",
"$defs": {
"foo": {
"$dynamicAnchor": "items",
"type": "string"
},
"list": {
"$id": "list",
"type": "array",
"items": { "$dynamicRef": "#items" },
"$defs": {
"items": {
"$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref",
"$anchor": "items",
"$dynamicAnchor": "foo"
}
}
}
}
},
"tests": [
{
"description": "Any array is valid",
"data": ["foo", 42],
"valid": true
}
]
},
{
"description": "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/relative-dynamic-reference/root",
"$dynamicAnchor": "meta",
"type": "object",
"properties": {
"foo": { "const": "pass" }
},
"$ref": "extended",
"$defs": {
"extended": {
"$id": "extended",
"$dynamicAnchor": "meta",
"type": "object",
"properties": {
"bar": { "$ref": "bar" }
}
},
"bar": {
"$id": "bar",
"type": "object",
"properties": {
"baz": { "$dynamicRef": "extended#meta" }
}
}
}
},
"tests": [
{
"description": "The recursive part is valid against the root",
"data": {
"foo": "pass",
"bar": {
"baz": { "foo": "pass" }
}
},
"valid": true
},
{
"description": "The recursive part is not valid against the root",
"data": {
"foo": "pass",
"bar": {
"baz": { "foo": "fail" }
}
},
"valid": false
}
]
},
{
"description": "A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor behaves like a normal $ref to $anchor",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/relative-dynamic-reference-without-bookend/root",
"$dynamicAnchor": "meta",
"type": "object",
"properties": {
"foo": { "const": "pass" }
},
"$ref": "extended",
"$defs": {
"extended": {
"$id": "extended",
"$anchor": "meta",
"type": "object",
"properties": {
"bar": { "$ref": "bar" }
}
},
"bar": {
"$id": "bar",
"type": "object",
"properties": {
"baz": { "$dynamicRef": "extended#meta" }
}
}
}
},
"tests": [
{
"description": "The recursive part doesn't need to validate against the root",
"data": {
"foo": "pass",
"bar": {
"baz": { "foo": "fail" }
}
},
"valid": true
}
]
},
{
"description": "multiple dynamic paths to the $dynamicRef keyword",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main",
"if": {
"properties": {
"kindOfList": { "const": "numbers" }
},
"required": ["kindOfList"]
},
"then": { "$ref": "numberList" },
"else": { "$ref": "stringList" },
"$defs": {
"genericList": {
"$id": "genericList",
"properties": {
"list": {
"items": { "$dynamicRef": "#itemType" }
}
},
"$defs": {
"defaultItemType": {
"$comment": "Only needed to satisfy bookending requirement",
"$dynamicAnchor": "itemType"
}
}
},
"numberList": {
"$id": "numberList",
"$defs": {
"itemType": {
"$dynamicAnchor": "itemType",
"type": "number"
}
},
"$ref": "genericList"
},
"stringList": {
"$id": "stringList",
"$defs": {
"itemType": {
"$dynamicAnchor": "itemType",
"type": "string"
}
},
"$ref": "genericList"
}
}
},
"tests": [
{
"description": "number list with number values",
"data": {
"kindOfList": "numbers",
"list": [1.1]
},
"valid": true
},
{
"description": "number list with string values",
"data": {
"kindOfList": "numbers",
"list": ["foo"]
},
"valid": false
},
{
"description": "string list with number values",
"data": {
"kindOfList": "strings",
"list": [1.1]
},
"valid": false
},
{
"description": "string list with string values",
"data": {
"kindOfList": "strings",
"list": ["foo"]
},
"valid": true
}
]
},
{
"description": "after leaving a dynamic scope, it is not used by a $dynamicRef",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main",
"if": {
"$id": "first_scope",
"$defs": {
"thingy": {
"$comment": "this is first_scope#thingy",
"$dynamicAnchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$ref": "start",
"$defs": {
"thingy": {
"$comment": "this is second_scope#thingy, the final destination of the $dynamicRef",
"$dynamicAnchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$comment": "this is the landing spot from $ref",
"$id": "start",
"$dynamicRef": "inner_scope#thingy"
},
"thingy": {
"$comment": "this is the first stop for the $dynamicRef",
"$id": "inner_scope",
"$dynamicAnchor": "thingy",
"type": "string"
}
}
},
"tests": [
{
"description": "string matches /$defs/thingy, but the $dynamicRef does not stop here",
"data": "a string",
"valid": false
},
{
"description": "first_scope is not in dynamic scope for the $dynamicRef",
"data": 42,
"valid": false
},
{
"description": "/then/$defs/thingy is the final stop for the $dynamicRef",
"data": null,
"valid": true
}
]
},
{
"description": "strict-tree schema, guards against misspelled properties",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/strict-tree.json",
"$dynamicAnchor": "node",
"$ref": "tree.json",
"unevaluatedProperties": false
},
"tests": [
{
"description": "instance with misspelled field",
"data": {
"children": [{
"daat": 1
}]
},
"valid": false
},
{
"description": "instance with correct field",
"data": {
"children": [{
"data": 1
}]
},
"valid": true
}
]
},
{
"description": "tests for implementation dynamic anchor and reference link",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/strict-extendible.json",
"$ref": "extendible-dynamic-ref.json",
"$defs": {
"elements": {
"$dynamicAnchor": "elements",
"properties": {
"a": true
},
"required": ["a"],
"additionalProperties": false
}
}
},
"tests": [
{
"description": "incorrect parent schema",
"data": {
"a": true
},
"valid": false
},
{
"description": "incorrect extended schema",
"data": {
"elements": [
{ "b": 1 }
]
},
"valid": false
},
{
"description": "correct extended schema",
"data": {
"elements": [
{ "a": 1 }
]
},
"valid": true
}
]
},
{
"description": "$ref and $dynamicAnchor are independent of order - $defs first",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json",
"allOf": [
{
"$ref": "extendible-dynamic-ref.json"
},
{
"$defs": {
"elements": {
"$dynamicAnchor": "elements",
"properties": {
"a": true
},
"required": ["a"],
"additionalProperties": false
}
}
}
]
},
"tests": [
{
"description": "incorrect parent schema",
"data": {
"a": true
},
"valid": false
},
{
"description": "incorrect extended schema",
"data": {
"elements": [
{ "b": 1 }
]
},
"valid": false
},
{
"description": "correct extended schema",
"data": {
"elements": [
{ "a": 1 }
]
},
"valid": true
}
]
},
{
"description": "$ref and $dynamicAnchor are independent of order - $ref first",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json",
"allOf": [
{
"$defs": {
"elements": {
"$dynamicAnchor": "elements",
"properties": {
"a": true
},
"required": ["a"],
"additionalProperties": false
}
}
},
{
"$ref": "extendible-dynamic-ref.json"
}
]
},
"tests": [
{
"description": "incorrect parent schema",
"data": {
"a": true
},
"valid": false
},
{
"description": "incorrect extended schema",
"data": {
"elements": [
{ "b": 1 }
]
},
"valid": false
},
{
"description": "correct extended schema",
"data": {
"elements": [
{ "a": 1 }
]
},
"valid": true
}
]
},
{
"description": "$ref to $dynamicRef finds detached $dynamicAnchor",
"schema": {
"$ref": "http://localhost:1234/draft2020-12/detached-dynamicref.json#/$defs/foo"
},
"tests": [
{
"description": "number is valid",
"data": 1,
"valid": true
},
{
"description": "non-number is invalid",
"data": "a",
"valid": false
}
]
},
{
"description": "$dynamicRef points to a boolean schema",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"true": true,
"false": false
},
"properties": {
"true": {
"$dynamicRef": "#/$defs/true"
},
"false": {
"$dynamicRef": "#/$defs/false"
}
}
},
"tests": [
{
"description": "follow $dynamicRef to a true schema",
"data": { "true": 1 },
"valid": true
},
{
"description": "follow $dynamicRef to a false schema",
"data": { "false": 1 },
"valid": false
}
]
},
{
"description": "$dynamicRef skips over intermediate resources - direct reference",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main",
"type": "object",
"properties": {
"bar-item": {
"$ref": "item"
}
},
"$defs": {
"bar": {
"$id": "bar",
"type": "array",
"items": {
"$ref": "item"
},
"$defs": {
"item": {
"$id": "item",
"type": "object",
"properties": {
"content": {
"$dynamicRef": "#content"
}
},
"$defs": {
"defaultContent": {
"$dynamicAnchor": "content",
"type": "integer"
}
}
},
"content": {
"$dynamicAnchor": "content",
"type": "string"
}
}
}
}
},
"tests": [
{
"description": "integer property passes",
"data": { "bar-item": { "content": 42 } },
"valid": true
},
{
"description": "string property fails",
"data": { "bar-item": { "content": "value" } },
"valid": false
}
]
}
]