diff --git a/benchmark/jsonschema.cc b/benchmark/jsonschema.cc index eeb6ecf96..3bf15345b 100644 --- a/benchmark/jsonschema.cc +++ b/benchmark/jsonschema.cc @@ -8,6 +8,20 @@ #include #include +static void Schema_Frame_WoT_Instances(benchmark::State &state) { + const auto schema{ + sourcemeta::core::read_json(std::filesystem::path{CURRENT_DIRECTORY} / + "schemas" / "draft7_w3c_wot_td_v1_1.json")}; + + for (auto _ : state) { + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + benchmark::DoNotOptimize(frame); + } +} + static void Schema_Frame_OMC_Instances(benchmark::State &state) { const auto schema{ sourcemeta::core::read_json(std::filesystem::path{CURRENT_DIRECTORY} / @@ -164,6 +178,7 @@ static void Schema_Bundle_Meta_2020_12(benchmark::State &state) { } } +BENCHMARK(Schema_Frame_WoT_Instances); BENCHMARK(Schema_Frame_OMC_Instances); BENCHMARK(Schema_Frame_OMC_References); BENCHMARK(Schema_Frame_OMC_Locations); diff --git a/benchmark/schemas/draft7_w3c_wot_td_v1_1.json b/benchmark/schemas/draft7_w3c_wot_td_v1_1.json new file mode 100644 index 000000000..178540ec2 --- /dev/null +++ b/benchmark/schemas/draft7_w3c_wot_td_v1_1.json @@ -0,0 +1,1362 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.sourcemeta.com/w3c/wot/v1.1/thing-description", + "title": "Thing Description", + "description": "JSON Schema for validating TD instances against the TD information model. TD instances can be with or without terms that have default values", + "type": "object", + "required": [ "title", "security", "securityDefinitions", "@context" ], + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "type": "object", + "required": [ "instance" ], + "properties": { + "instance": { + "type": "string" + } + } + }, + "links": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/link_element" + }, + { + "$ref": "#/definitions/icon_link_element" + } + ] + } + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_root" + } + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + } + }, + "schemaDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "profile": { + "oneOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/anyUri" + } + } + ] + }, + "security": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ] + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + } + }, + "additionalProperties": true, + "definitions": { + "anyUri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "security": { + "oneOf": [ + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "examples": [ "longpoll", "websub", "sse" ], + "type": "string" + }, + "thing-context-td-uri-v1": { + "type": "string", + "const": "https://www.w3.org/2019/wot/td/v1" + }, + "thing-context-td-uri-v1.1": { + "type": "string", + "const": "https://www.w3.org/2022/wot/td/v1.1" + }, + "thing-context-td-uri-temp": { + "type": "string", + "const": "http://www.w3.org/ns/td" + }, + "thing-context": { + "anyOf": [ + { + "$comment": "New context URI with other vocabularies after it but not the old one", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ], + "not": { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + } + }, + { + "$comment": "Only the new context URI", + "$ref": "#/definitions/thing-context-td-uri-v1.1" + }, + { + "$comment": "Old context URI, followed by the new one and possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "minItems": 2, + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + }, + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Old context URI, followed by possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "minItems": 1, + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Only the old context URI", + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ] + }, + "bcp47_string": { + "type": "string", + "pattern": "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + }, + "type_declaration": { + "oneOf": [ + { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + }, + { + "type": "array", + "items": { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + } + } + ] + }, + "dataSchema-type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalResponsesDefinition": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "schema": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "multipleOfDefinition": { + "type": [ "integer", "number" ], + "exclusiveMinimum": 0 + }, + "expectedResponse": { + "type": "object", + "required": [ "contentType" ], + "properties": { + "contentType": { + "type": "string" + } + } + }, + "form_element_base": { + "type": "object", + "required": [ "href" ], + "properties": { + "op": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "$ref": "#/definitions/expectedResponse" + }, + "additionalResponses": { + "$ref": "#/definitions/additionalResponsesDefinition" + } + }, + "additionalProperties": true + }, + "form_element_property": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + } + } + ] + } + }, + "additionalProperties": true + }, + "form_element_action": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ "invokeaction", "queryaction", "cancelaction" ] + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ "invokeaction", "queryaction", "cancelaction" ] + } + } + ] + } + }, + "additionalProperties": true + }, + "form_element_event": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ "subscribeevent", "unsubscribeevent" ] + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ "subscribeevent", "unsubscribeevent" ] + } + } + ] + } + }, + "additionalProperties": true + }, + "form_element_root": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "required": [ "op" ], + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + } + } + ] + } + }, + "additionalProperties": true + }, + "form": { + "$comment": "This is NOT for validation purposes but for automatic generation of TS types. For more info, please see: https://github.com/w3c/wot-thing-description/pull/1319#issuecomment-994950057", + "oneOf": [ + { + "$ref": "#/definitions/form_element_property" + }, + { + "$ref": "#/definitions/form_element_action" + }, + { + "$ref": "#/definitions/form_element_event" + }, + { + "$ref": "#/definitions/form_element_root" + } + ] + }, + "property_element": { + "type": "object", + "required": [ "forms" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "observable": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": true + }, + "action_element": { + "type": "object", + "required": [ "forms" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "type": "boolean" + }, + "idempotent": { + "type": "boolean" + }, + "synchronous": { + "type": "boolean" + } + }, + "additionalProperties": true + }, + "event_element": { + "type": "object", + "required": [ "forms" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "dataResponse": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + } + }, + "additionalProperties": true + }, + "base_link_element": { + "type": "object", + "required": [ "href" ], + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + }, + "hreflang": { + "anyOf": [ + { + "$ref": "#/definitions/bcp47_string" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/bcp47_string" + } + } + ] + } + }, + "additionalProperties": true + }, + "link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "not": { + "description": "A basic link element should not contain sizes", + "type": "object", + "required": [ "sizes" ], + "properties": { + "sizes": {} + } + } + }, + { + "not": { + "description": "A basic link element should not contain icon or tm:extends", + "required": [ "rel" ], + "properties": { + "rel": { + "enum": [ "icon", "tm:extends" ] + } + } + } + } + ] + }, + "icon_link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "required": [ "rel" ], + "properties": { + "rel": { + "const": "icon" + }, + "sizes": { + "type": "string", + "pattern": "[0-9]*x[0-9]+" + } + } + } + ] + }, + "additionalSecurityScheme": { + "description": "Applies to additional SecuritySchemes not defined in the WoT TD specification.", + "$comment": "Additional SecuritySchemes should always be defined via a context extension, using a prefixed value for the scheme. This prefix (e.g. 'ace', see the example below) must contain at least one character in order to reference a valid JSON-LD context extension.", + "examples": [ + { + "scheme": "ace:ACESecurityScheme", + "ace:as": "coaps://as.example.com/token", + "ace:audience": "coaps://rs.example.com", + "ace:scopes": [ "limited", "special" ], + "ace:cnonce": true + } + ], + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "pattern": ".+:.*" + } + }, + "additionalProperties": true + }, + "noSecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "nosec" ] + } + }, + "additionalProperties": true + }, + "autoSecurityScheme": { + "type": "object", + "not": { + "required": [ "name" ] + }, + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "auto" ] + } + }, + "additionalProperties": true + }, + "comboSecurityScheme": { + "oneOf": [ + { + "type": "object", + "required": [ "scheme", "oneOf" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "combo" ] + }, + "oneOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + } + }, + "additionalProperties": true + }, + { + "type": "object", + "required": [ "scheme", "allOf" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "combo" ] + }, + "allOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + } + }, + "additionalProperties": true + } + ] + }, + "basicSecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "basic" ] + }, + "in": { + "type": "string", + "enum": [ "header", "query", "body", "cookie", "auto" ] + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + }, + "digestSecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "digest" ] + }, + "qop": { + "type": "string", + "enum": [ "auth", "auth-int" ] + }, + "in": { + "type": "string", + "enum": [ "header", "query", "body", "cookie", "auto" ] + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + }, + "apiKeySecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "apikey" ] + }, + "in": { + "type": "string", + "enum": [ "header", "query", "body", "cookie", "uri", "auto" ] + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + }, + "bearerSecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "bearer" ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ "header", "query", "body", "cookie", "auto" ] + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + }, + "pskSecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "psk" ] + }, + "identity": { + "type": "string" + } + }, + "additionalProperties": true + }, + "oAuth2SecurityScheme": { + "type": "object", + "required": [ "scheme" ], + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ "oauth2" ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "flow": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ "code", "client" ] + } + ] + } + }, + "additionalProperties": true + }, + "securityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/noSecurityScheme" + }, + { + "$ref": "#/definitions/autoSecurityScheme" + }, + { + "$ref": "#/definitions/comboSecurityScheme" + }, + { + "$ref": "#/definitions/basicSecurityScheme" + }, + { + "$ref": "#/definitions/digestSecurityScheme" + }, + { + "$ref": "#/definitions/apiKeySecurityScheme" + }, + { + "$ref": "#/definitions/bearerSecurityScheme" + }, + { + "$ref": "#/definitions/pskSecurityScheme" + }, + { + "$ref": "#/definitions/oAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/additionalSecurityScheme" + } + ] + } + }, + "version": "1.1-12-March-2025" +} diff --git a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h index 0fdfcc7f3..e51aa8962 100644 --- a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h +++ b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h @@ -244,6 +244,131 @@ template class GenericPointerTemplate { return this->data.size(); } + /// Access a token at a given index. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + /// assert(pointer.at(1) == sourcemeta::core::Pointer::Token{"bar"}); + /// ``` + [[nodiscard]] auto at(const size_type index) const -> const_reference { + assert(index < this->data.size()); + return this->data[index]; + } + + /// Check if this JSON Pointer template starts with another JSON Pointer + /// template. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + /// const sourcemeta::core::PointerTemplate prefix{"foo", "bar"}; + /// assert(pointer.starts_with(prefix)); + /// ``` + [[nodiscard]] auto + starts_with(const GenericPointerTemplate &prefix) const noexcept + -> bool { + if (prefix.size() > this->size()) { + return false; + } + + auto this_iterator{this->data.cbegin()}; + auto prefix_iterator{prefix.cbegin()}; + while (prefix_iterator != prefix.cend()) { + if (!(*this_iterator == *prefix_iterator)) { + return false; + } + + ++this_iterator; + ++prefix_iterator; + } + + return true; + } + + /// Check if this JSON Pointer template ends with another JSON Pointer + /// template. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + /// const sourcemeta::core::PointerTemplate suffix{"bar", "baz"}; + /// assert(pointer.ends_with(suffix)); + /// ``` + [[nodiscard]] auto + ends_with(const GenericPointerTemplate &suffix) const noexcept + -> bool { + if (suffix.size() > this->size()) { + return false; + } + + auto this_iterator{this->data.crbegin()}; + auto suffix_iterator{suffix.crbegin()}; + while (suffix_iterator != suffix.crend()) { + if (!(*this_iterator == *suffix_iterator)) { + return false; + } + + ++this_iterator; + ++suffix_iterator; + } + + return true; + } + + /// Get a new JSON Pointer template consisting of the first N tokens. For + /// example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + /// const sourcemeta::core::PointerTemplate expected{"foo", "bar"}; + /// assert(pointer.head(2) == expected); + /// ``` + [[nodiscard]] auto head(const size_type count) const + -> GenericPointerTemplate { + assert(count <= this->data.size()); + GenericPointerTemplate result; + result.data.reserve(count); + for (size_type index = 0; index < count; ++index) { + result.data.push_back(this->data[index]); + } + + return result; + } + + /// Get a new JSON Pointer template consisting of the last N tokens. For + /// example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + /// const sourcemeta::core::PointerTemplate expected{"bar", "baz"}; + /// assert(pointer.tail(2) == expected); + /// ``` + [[nodiscard]] auto tail(const size_type count) const + -> GenericPointerTemplate { + assert(count <= this->data.size()); + GenericPointerTemplate result; + result.data.reserve(count); + const auto start{this->data.size() - count}; + for (size_type index = start; index < this->data.size(); ++index) { + result.data.push_back(this->data[index]); + } + + return result; + } + /// Check if a JSON Pointer template only consists in normal non-templated /// tokens. For example: /// diff --git a/src/core/jsonschema/frame.cc b/src/core/jsonschema/frame.cc index 9f9bf66cc..bce9d6e1c 100644 --- a/src/core/jsonschema/frame.cc +++ b/src/core/jsonschema/frame.cc @@ -2,8 +2,9 @@ #include // std::sort, std::all_of, std::any_of #include // assert -#include // std::less +#include // std::less, std::plus #include // std::map +#include // std::transform_reduce #include // std::optional #include // std::ostringstream #include // std::unordered_map @@ -263,6 +264,26 @@ struct InternalEntry { std::optional id; }; +auto is_recursive_extension( + const sourcemeta::core::PointerTemplate &new_path, + const std::vector &existing_paths) + -> bool { + for (const auto &existing : existing_paths) { + if (!new_path.starts_with(existing)) { + continue; + } + + const auto suffix{new_path.tail(new_path.size() - existing.size())}; + for (const auto &other : existing_paths) { + if (other.ends_with(suffix)) { + return true; + } + } + } + + return false; +} + auto traverse_origin_instance_locations( const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Instances &instances, @@ -273,6 +294,7 @@ auto traverse_origin_instance_locations( const sourcemeta::core::SchemaFrame::References::value_type *> &visited) -> void { if (accumulator.has_value() && + !is_recursive_extension(accumulator.value(), destination) && std::ranges::find(destination, accumulator.value()) == destination.cend()) { destination.push_back(accumulator.value()); @@ -321,52 +343,33 @@ auto is_definition_entry(const sourcemeta::core::Pointer &pointer) -> bool { container.to_property() == "definitions"); } -auto repopulate_instance_locations( - const sourcemeta::core::SchemaFrame &frame, +auto repopulate_instance_locations_single_level( const sourcemeta::core::SchemaFrame::Instances &instances, - const std::unordered_map &cache, const sourcemeta::core::Pointer &pointer, const CacheSubschema &cache_entry, - sourcemeta::core::SchemaFrame::Instances::mapped_type &destination, - const std::optional &accumulator) + sourcemeta::core::SchemaFrame::Instances::mapped_type &destination) -> void { - // Definition entries should not inherit instance locations from their parent - // container. They only get instance locations if something references them. - // However, children of definitions should still inherit from their definition - // parent if (cache_entry.orphan && is_definition_entry(pointer)) { return; } - if (cache_entry.parent.has_value() && - // Don't consider bases from the root subschema, as if that - // subschema has any instance location other than "", then it - // indicates a recursive reference - !cache_entry.parent.value().empty()) { - const auto match{instances.find(cache_entry.parent.value())}; - if (match == instances.cend()) { - return; - } - - for (const auto &parent_instance_location : match->second) { - auto new_accumulator = cache_entry.relative_instance_location; - if (accumulator.has_value()) { - for (const auto &token : accumulator.value()) { - new_accumulator.emplace_back(token); - } - } + if (!cache_entry.parent.has_value() || cache_entry.parent.value().empty()) { + return; + } - auto result = parent_instance_location; - for (const auto &token : new_accumulator) { - result.emplace_back(token); - } + const auto match{instances.find(cache_entry.parent.value())}; + if (match == instances.cend()) { + return; + } - if (std::ranges::find(destination, result) == destination.cend()) { - destination.push_back(result); - } + for (const auto &parent_instance_location : match->second) { + auto result = parent_instance_location; + for (const auto &token : cache_entry.relative_instance_location) { + result.emplace_back(token); + } - repopulate_instance_locations( - frame, instances, cache, cache_entry.parent.value(), - cache.at(cache_entry.parent.value()), destination, new_accumulator); + if (!is_recursive_extension(result, destination) && + std::ranges::find(destination, result) == destination.cend()) { + destination.push_back(result); } } } @@ -1057,8 +1060,6 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, } if (this->mode_ == sourcemeta::core::SchemaFrame::Mode::Instances) { - // First pass: trace through references to find instance locations. - // This handles definitions that are referenced for (auto &entry : this->locations_) { if (entry.second.type == SchemaFrame::LocationType::Pointer) { continue; @@ -1070,32 +1071,39 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, this->instances_[entry.second.pointer], visited); } - // Second pass: inherit instance locations from parents (top-down). - // This handles applicator children inheriting from their parent schema - for (auto &entry : this->locations_) { - if (entry.second.type == SchemaFrame::LocationType::Pointer) { - continue; + std::size_t previous_count{0}; + std::size_t current_count{std::transform_reduce( + this->instances_.cbegin(), this->instances_.cend(), std::size_t{0}, + std::plus<>{}, [](const auto &entry) { return entry.second.size(); })}; + + while (current_count != previous_count) { + previous_count = current_count; + + for (auto &entry : this->locations_) { + if (entry.second.type == SchemaFrame::LocationType::Pointer) { + continue; + } + + const auto subschema{subschemas.find(entry.second.pointer)}; + repopulate_instance_locations_single_level( + this->instances_, subschema->first, subschema->second, + this->instances_[entry.second.pointer]); } - const auto subschema{subschemas.find(entry.second.pointer)}; - repopulate_instance_locations(*this, this->instances_, subschemas, - subschema->first, subschema->second, - this->instances_[entry.second.pointer], - std::nullopt); - } + for (auto &entry : this->locations_) { + if (entry.second.type == SchemaFrame::LocationType::Pointer) { + continue; + } - // Third pass: trace references again. Now that inheritance has run, - // schemas from definitions can trace to applicator children that now have - // instance locations from inheritance - for (auto &entry : this->locations_) { - if (entry.second.type == SchemaFrame::LocationType::Pointer) { - continue; + std::unordered_set visited; + traverse_origin_instance_locations( + *this, this->instances_, entry.second.pointer, std::nullopt, + this->instances_[entry.second.pointer], visited); } - std::unordered_set visited; - traverse_origin_instance_locations( - *this, this->instances_, entry.second.pointer, std::nullopt, - this->instances_[entry.second.pointer], visited); + current_count = std::transform_reduce( + this->instances_.cbegin(), this->instances_.cend(), std::size_t{0}, + std::plus<>{}, [](const auto &entry) { return entry.second.size(); }); } } } diff --git a/test/jsonpointer/jsonpointer_template_test.cc b/test/jsonpointer/jsonpointer_template_test.cc index f76ea2040..9aaf4c4ca 100644 --- a/test/jsonpointer/jsonpointer_template_test.cc +++ b/test/jsonpointer/jsonpointer_template_test.cc @@ -938,3 +938,213 @@ TEST(JSONPointer_template, matches_conditional_property_wildcard) { EXPECT_TRUE(left.matches(right)); EXPECT_TRUE(right.matches(left)); } + +TEST(JSONPointer_template, at_first) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const auto &token{pointer.at(0)}; + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_EQ(std::get(token), + sourcemeta::core::Pointer::Token{"foo"}); +} + +TEST(JSONPointer_template, at_middle) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const auto &token{pointer.at(1)}; + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_EQ(std::get(token), + sourcemeta::core::Pointer::Token{"bar"}); +} + +TEST(JSONPointer_template, at_last) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const auto &token{pointer.at(2)}; + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_EQ(std::get(token), + sourcemeta::core::Pointer::Token{"baz"}); +} + +TEST(JSONPointer_template, at_with_wildcard) { + sourcemeta::core::PointerTemplate pointer; + pointer.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + pointer.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Property); + pointer.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + const auto &wildcard{pointer.at(1)}; + EXPECT_TRUE( + std::holds_alternative( + wildcard)); + EXPECT_EQ(std::get(wildcard), + sourcemeta::core::PointerTemplate::Wildcard::Property); +} + +TEST(JSONPointer_template, starts_with_true_same) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate prefix{"foo", "bar"}; + EXPECT_TRUE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_true_prefix) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate prefix{"foo", "bar"}; + EXPECT_TRUE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_true_empty_prefix) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate prefix; + EXPECT_TRUE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_true_single) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate prefix{"foo"}; + EXPECT_TRUE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_false_different) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate prefix{"foo", "qux"}; + EXPECT_FALSE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_false_longer) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate prefix{"foo", "bar", "baz"}; + EXPECT_FALSE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, starts_with_with_wildcard) { + sourcemeta::core::PointerTemplate pointer; + pointer.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + pointer.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + pointer.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + sourcemeta::core::PointerTemplate prefix; + prefix.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + prefix.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + + EXPECT_TRUE(pointer.starts_with(prefix)); +} + +TEST(JSONPointer_template, ends_with_true_same) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate suffix{"foo", "bar"}; + EXPECT_TRUE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_true_suffix) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate suffix{"bar", "baz"}; + EXPECT_TRUE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_true_empty_suffix) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate suffix; + EXPECT_TRUE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_true_single) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate suffix{"baz"}; + EXPECT_TRUE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_false_different) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate suffix{"bar", "qux"}; + EXPECT_FALSE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_false_longer) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar"}; + const sourcemeta::core::PointerTemplate suffix{"foo", "bar", "baz"}; + EXPECT_FALSE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, ends_with_with_wildcard) { + sourcemeta::core::PointerTemplate pointer; + pointer.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + pointer.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + pointer.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + sourcemeta::core::PointerTemplate suffix; + suffix.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + suffix.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + EXPECT_TRUE(pointer.ends_with(suffix)); +} + +TEST(JSONPointer_template, head_zero) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const auto result{pointer.head(0)}; + EXPECT_TRUE(result.empty()); +} + +TEST(JSONPointer_template, head_one) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"foo"}; + EXPECT_EQ(pointer.head(1), expected); +} + +TEST(JSONPointer_template, head_two) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"foo", "bar"}; + EXPECT_EQ(pointer.head(2), expected); +} + +TEST(JSONPointer_template, head_all) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"foo", "bar", "baz"}; + EXPECT_EQ(pointer.head(3), expected); +} + +TEST(JSONPointer_template, head_with_wildcard) { + sourcemeta::core::PointerTemplate pointer; + pointer.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + pointer.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + pointer.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + sourcemeta::core::PointerTemplate expected; + expected.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + expected.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + + EXPECT_EQ(pointer.head(2), expected); +} + +TEST(JSONPointer_template, tail_zero) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const auto result{pointer.tail(0)}; + EXPECT_TRUE(result.empty()); +} + +TEST(JSONPointer_template, tail_one) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"baz"}; + EXPECT_EQ(pointer.tail(1), expected); +} + +TEST(JSONPointer_template, tail_two) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"bar", "baz"}; + EXPECT_EQ(pointer.tail(2), expected); +} + +TEST(JSONPointer_template, tail_all) { + const sourcemeta::core::PointerTemplate pointer{"foo", "bar", "baz"}; + const sourcemeta::core::PointerTemplate expected{"foo", "bar", "baz"}; + EXPECT_EQ(pointer.tail(3), expected); +} + +TEST(JSONPointer_template, tail_with_wildcard) { + sourcemeta::core::PointerTemplate pointer; + pointer.emplace_back(sourcemeta::core::Pointer::Token{"foo"}); + pointer.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + pointer.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + sourcemeta::core::PointerTemplate expected; + expected.emplace_back(sourcemeta::core::PointerTemplate::Wildcard::Item); + expected.emplace_back(sourcemeta::core::Pointer::Token{"bar"}); + + EXPECT_EQ(pointer.tail(2), expected); +} diff --git a/test/jsonschema/jsonschema_frame_2020_12_test.cc b/test/jsonschema/jsonschema_frame_2020_12_test.cc index 63c444413..d953e6f4b 100644 --- a/test/jsonschema/jsonschema_frame_2020_12_test.cc +++ b/test/jsonschema/jsonschema_frame_2020_12_test.cc @@ -3403,3 +3403,642 @@ TEST(JSONSchema_frame_2020_12, circular_ref_through_defs) { frame, "/$defs/b/$ref", "https://www.sourcemeta.com/schema#/$defs/a", "https://www.sourcemeta.com/schema", "/$defs/a", "#/$defs/a"); } + +TEST(JSONSchema_frame_2020_12, allof_ref_to_def_with_properties_ref) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$id": "https://www.sourcemeta.com/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "$ref": "#/$defs/bar" } + }, + "$defs": { + "bar": { + "allOf": [ + { "$ref": "#/$defs/baz" } + ] + }, + "baz": { + "type": "object", + "properties": { + "qux": { "$ref": "#/$defs/extra" } + } + }, + "extra": { "type": "string" } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + + EXPECT_EQ(frame.locations().size(), 18); + + EXPECT_FRAME_STATIC_2020_12_RESOURCE( + frame, "https://www.sourcemeta.com/schema", + "https://www.sourcemeta.com/schema", "", + "https://www.sourcemeta.com/schema", "", {""}, std::nullopt); + + // Subschemas + + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/properties/foo", + "https://www.sourcemeta.com/schema", "/properties/foo", + "https://www.sourcemeta.com/schema", "/properties/foo", {"/foo"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/bar", + "https://www.sourcemeta.com/schema", "/$defs/bar", + "https://www.sourcemeta.com/schema", "/$defs/bar", {"/foo"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/bar/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf/0", {"/foo"}, + "/$defs/bar"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/baz", + "https://www.sourcemeta.com/schema", "/$defs/baz", + "https://www.sourcemeta.com/schema", "/$defs/baz", {"/foo"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/baz/properties/qux", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties/qux", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties/qux", + {"/foo/qux"}, "/$defs/baz"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/extra", + "https://www.sourcemeta.com/schema", "/$defs/extra", + "https://www.sourcemeta.com/schema", "/$defs/extra", {"/foo/qux"}, ""); + + // JSON Pointers + + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$id", + "https://www.sourcemeta.com/schema", "/$id", + "https://www.sourcemeta.com/schema", "/$id", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$schema", + "https://www.sourcemeta.com/schema", "/$schema", + "https://www.sourcemeta.com/schema", "/$schema", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties", + "https://www.sourcemeta.com/schema", "/properties", + "https://www.sourcemeta.com/schema", "/properties", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties/foo/$ref", + "https://www.sourcemeta.com/schema", "/properties/foo/$ref", + "https://www.sourcemeta.com/schema", "/properties/foo/$ref", {}, + "/properties/foo"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs", + "https://www.sourcemeta.com/schema", "/$defs", + "https://www.sourcemeta.com/schema", "/$defs", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/bar/allOf", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf", {}, + "/$defs/bar"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/bar/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/bar/allOf/0/$ref", {}, + "/$defs/bar/allOf/0"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/baz/type", + "https://www.sourcemeta.com/schema", "/$defs/baz/type", + "https://www.sourcemeta.com/schema", "/$defs/baz/type", {}, "/$defs/baz"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/baz/properties", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties", {}, + "/$defs/baz"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/baz/properties/qux/$ref", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties/qux/$ref", + "https://www.sourcemeta.com/schema", "/$defs/baz/properties/qux/$ref", {}, + "/$defs/baz/properties/qux"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/extra/type", + "https://www.sourcemeta.com/schema", "/$defs/extra/type", + "https://www.sourcemeta.com/schema", "/$defs/extra/type", {}, + "/$defs/extra"); + + // References + + EXPECT_EQ(frame.references().size(), 4); + + EXPECT_STATIC_REFERENCE( + frame, "/$schema", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", std::nullopt, + "https://json-schema.org/draft/2020-12/schema"); + EXPECT_STATIC_REFERENCE(frame, "/properties/foo/$ref", + "https://www.sourcemeta.com/schema#/$defs/bar", + "https://www.sourcemeta.com/schema", "/$defs/bar", + "#/$defs/bar"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/bar/allOf/0/$ref", + "https://www.sourcemeta.com/schema#/$defs/baz", + "https://www.sourcemeta.com/schema", "/$defs/baz", + "#/$defs/baz"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/baz/properties/qux/$ref", + "https://www.sourcemeta.com/schema#/$defs/extra", + "https://www.sourcemeta.com/schema", "/$defs/extra", + "#/$defs/extra"); +} + +TEST(JSONSchema_frame_2020_12, deep_allof_ref_chain) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$id": "https://www.sourcemeta.com/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "start": { "$ref": "#/$defs/L1" } + }, + "$defs": { + "L1": { "allOf": [ { "$ref": "#/$defs/L2" } ] }, + "L2": { "properties": { "a": { "$ref": "#/$defs/L3" } } }, + "L3": { "allOf": [ { "$ref": "#/$defs/L4" } ] }, + "L4": { "properties": { "b": { "$ref": "#/$defs/L5" } } }, + "L5": { "type": "string" } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + + EXPECT_EQ(frame.locations().size(), 25); + + EXPECT_FRAME_STATIC_2020_12_RESOURCE( + frame, "https://www.sourcemeta.com/schema", + "https://www.sourcemeta.com/schema", "", + "https://www.sourcemeta.com/schema", "", {""}, std::nullopt); + + // Subschemas + + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/properties/start", + "https://www.sourcemeta.com/schema", "/properties/start", + "https://www.sourcemeta.com/schema", "/properties/start", {"/start"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L1", + "https://www.sourcemeta.com/schema", "/$defs/L1", + "https://www.sourcemeta.com/schema", "/$defs/L1", {"/start"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L1/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf/0", {"/start"}, + "/$defs/L1"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L2", + "https://www.sourcemeta.com/schema", "/$defs/L2", + "https://www.sourcemeta.com/schema", "/$defs/L2", {"/start"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L2/properties/a", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties/a", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties/a", + {"/start/a"}, "/$defs/L2"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L3", + "https://www.sourcemeta.com/schema", "/$defs/L3", + "https://www.sourcemeta.com/schema", "/$defs/L3", {"/start/a"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L3/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf/0", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf/0", {"/start/a"}, + "/$defs/L3"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L4", + "https://www.sourcemeta.com/schema", "/$defs/L4", + "https://www.sourcemeta.com/schema", "/$defs/L4", {"/start/a"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L4/properties/b", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties/b", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties/b", + {"/start/a/b"}, "/$defs/L4"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/L5", + "https://www.sourcemeta.com/schema", "/$defs/L5", + "https://www.sourcemeta.com/schema", "/$defs/L5", {"/start/a/b"}, ""); + + // JSON Pointers + + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$id", + "https://www.sourcemeta.com/schema", "/$id", + "https://www.sourcemeta.com/schema", "/$id", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$schema", + "https://www.sourcemeta.com/schema", "/$schema", + "https://www.sourcemeta.com/schema", "/$schema", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties", + "https://www.sourcemeta.com/schema", "/properties", + "https://www.sourcemeta.com/schema", "/properties", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties/start/$ref", + "https://www.sourcemeta.com/schema", "/properties/start/$ref", + "https://www.sourcemeta.com/schema", "/properties/start/$ref", {}, + "/properties/start"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs", + "https://www.sourcemeta.com/schema", "/$defs", + "https://www.sourcemeta.com/schema", "/$defs", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L1/allOf", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf", {}, "/$defs/L1"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L1/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L1/allOf/0/$ref", {}, + "/$defs/L1/allOf/0"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L2/properties", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties", {}, + "/$defs/L2"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L2/properties/a/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties/a/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L2/properties/a/$ref", {}, + "/$defs/L2/properties/a"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L3/allOf", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf", {}, "/$defs/L3"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L3/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf/0/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L3/allOf/0/$ref", {}, + "/$defs/L3/allOf/0"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L4/properties", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties", {}, + "/$defs/L4"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L4/properties/b/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties/b/$ref", + "https://www.sourcemeta.com/schema", "/$defs/L4/properties/b/$ref", {}, + "/$defs/L4/properties/b"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/L5/type", + "https://www.sourcemeta.com/schema", "/$defs/L5/type", + "https://www.sourcemeta.com/schema", "/$defs/L5/type", {}, "/$defs/L5"); + + // References + + EXPECT_EQ(frame.references().size(), 6); + + EXPECT_STATIC_REFERENCE( + frame, "/$schema", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", std::nullopt, + "https://json-schema.org/draft/2020-12/schema"); + EXPECT_STATIC_REFERENCE(frame, "/properties/start/$ref", + "https://www.sourcemeta.com/schema#/$defs/L1", + "https://www.sourcemeta.com/schema", "/$defs/L1", + "#/$defs/L1"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/L1/allOf/0/$ref", + "https://www.sourcemeta.com/schema#/$defs/L2", + "https://www.sourcemeta.com/schema", "/$defs/L2", + "#/$defs/L2"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/L2/properties/a/$ref", + "https://www.sourcemeta.com/schema#/$defs/L3", + "https://www.sourcemeta.com/schema", "/$defs/L3", + "#/$defs/L3"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/L3/allOf/0/$ref", + "https://www.sourcemeta.com/schema#/$defs/L4", + "https://www.sourcemeta.com/schema", "/$defs/L4", + "#/$defs/L4"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/L4/properties/b/$ref", + "https://www.sourcemeta.com/schema#/$defs/L5", + "https://www.sourcemeta.com/schema", "/$defs/L5", + "#/$defs/L5"); +} + +TEST(JSONSchema_frame_2020_12, recursive_ref_to_self) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$id": "https://www.sourcemeta.com/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "node": { + "properties": { + "children": { + "items": { "$ref": "#/$defs/node" } + } + } + } + }, + "properties": { + "root": { "$ref": "#/$defs/node" } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + + EXPECT_EQ(frame.locations().size(), 12); + + // Resource + + EXPECT_FRAME_STATIC_2020_12_RESOURCE( + frame, "https://www.sourcemeta.com/schema", + "https://www.sourcemeta.com/schema", "", + "https://www.sourcemeta.com/schema", "", {""}, std::nullopt); + + // Subschemas + + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/properties/root", + "https://www.sourcemeta.com/schema", "/properties/root", + "https://www.sourcemeta.com/schema", "/properties/root", {"/root"}, ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, "https://www.sourcemeta.com/schema#/$defs/node", + "https://www.sourcemeta.com/schema", "/$defs/node", + "https://www.sourcemeta.com/schema", "/$defs/node", + POINTER_TEMPLATES("/root", "/root/children/~?items~/~I~"), ""); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, + "https://www.sourcemeta.com/schema#/$defs/node/properties/children", + "https://www.sourcemeta.com/schema", "/$defs/node/properties/children", + "https://www.sourcemeta.com/schema", "/$defs/node/properties/children", + POINTER_TEMPLATES("/root/children", + "/root/children/~?items~/~I~/children"), + "/$defs/node"); + EXPECT_FRAME_STATIC_2020_12_SUBSCHEMA( + frame, + "https://www.sourcemeta.com/schema#/$defs/node/properties/children/items", + "https://www.sourcemeta.com/schema", + "/$defs/node/properties/children/items", + "https://www.sourcemeta.com/schema", + "/$defs/node/properties/children/items", {"/root/children/~?items~/~I~"}, + "/$defs/node/properties/children"); + + // Pointers + + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$id", + "https://www.sourcemeta.com/schema", "/$id", + "https://www.sourcemeta.com/schema", "/$id", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$schema", + "https://www.sourcemeta.com/schema", "/$schema", + "https://www.sourcemeta.com/schema", "/$schema", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs", + "https://www.sourcemeta.com/schema", "/$defs", + "https://www.sourcemeta.com/schema", "/$defs", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties", + "https://www.sourcemeta.com/schema", "/properties", + "https://www.sourcemeta.com/schema", "/properties", {}, ""); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/properties/root/$ref", + "https://www.sourcemeta.com/schema", "/properties/root/$ref", + "https://www.sourcemeta.com/schema", "/properties/root/$ref", {}, + "/properties/root"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, "https://www.sourcemeta.com/schema#/$defs/node/properties", + "https://www.sourcemeta.com/schema", "/$defs/node/properties", + "https://www.sourcemeta.com/schema", "/$defs/node/properties", {}, + "/$defs/node"); + EXPECT_FRAME_STATIC_2020_12_POINTER( + frame, + "https://www.sourcemeta.com/schema#/$defs/node/properties/children/items/" + "$ref", + "https://www.sourcemeta.com/schema", + "/$defs/node/properties/children/items/$ref", + "https://www.sourcemeta.com/schema", + "/$defs/node/properties/children/items/$ref", {}, + "/$defs/node/properties/children/items"); + + // References + + EXPECT_EQ(frame.references().size(), 3); + + EXPECT_STATIC_REFERENCE( + frame, "/$schema", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", std::nullopt, + "https://json-schema.org/draft/2020-12/schema"); + EXPECT_STATIC_REFERENCE(frame, "/properties/root/$ref", + "https://www.sourcemeta.com/schema#/$defs/node", + "https://www.sourcemeta.com/schema", "/$defs/node", + "#/$defs/node"); + EXPECT_STATIC_REFERENCE(frame, "/$defs/node/properties/children/items/$ref", + "https://www.sourcemeta.com/schema#/$defs/node", + "https://www.sourcemeta.com/schema", "/$defs/node", + "#/$defs/node"); +} + +TEST(JSONSchema_frame_2020_12, nested_properties_repeated_names) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "a": { + "properties": { + "b": { + "properties": { + "a": { + "properties": { + "b": { + "type": "string" + } + } + } + } + } + } + } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + + EXPECT_EQ(frame.locations().size(), 11); + + // Subschemas + + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "", "", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {""}, std::nullopt); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a", "/properties/a", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a"}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a/properties/b", "/properties/a/properties/b", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b"}, + "/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a/properties/b/properties/a", + "/properties/a/properties/b/properties/a", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b/a"}, + "/properties/a/properties/b"); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a/properties/b/properties/a/properties/b", + "/properties/a/properties/b/properties/a/properties/b", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b/a/b"}, + "/properties/a/properties/b/properties/a"); + + // Pointers + + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$schema", "/$schema", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties", "/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties", "/properties/a/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, "/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties/b/properties", + "/properties/a/properties/b/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/properties/a/properties/b"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties/b/properties/a/properties", + "/properties/a/properties/b/properties/a/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/properties/a/properties/b/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties/b/properties/a/properties/b/type", + "/properties/a/properties/b/properties/a/properties/b/type", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/properties/a/properties/b/properties/a/properties/b"); + + // References + + EXPECT_EQ(frame.references().size(), 1); + + EXPECT_STATIC_REFERENCE( + frame, "/$schema", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", std::nullopt, + "https://json-schema.org/draft/2020-12/schema"); +} + +TEST(JSONSchema_frame_2020_12, non_recursive_ref_repeated_segment) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "inner": { + "properties": { + "a": { + "properties": { + "b": { "type": "string" } + } + } + } + } + }, + "properties": { + "a": { + "properties": { + "b": { "$ref": "#/$defs/inner" } + } + } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Instances}; + frame.analyse(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver); + + EXPECT_EQ(frame.locations().size(), 14); + + // Subschemas + + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "", "", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {""}, std::nullopt); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/$defs/inner", "/$defs/inner", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b"}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/$defs/inner/properties/a", "/$defs/inner/properties/a", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b/a"}, + "/$defs/inner"); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/$defs/inner/properties/a/properties/b", + "/$defs/inner/properties/a/properties/b", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b/a/b"}, + "/$defs/inner/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a", "/properties/a", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a"}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA( + frame, "#/properties/a/properties/b", "/properties/a/properties/b", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {"/a/b"}, + "/properties/a"); + + // Pointers + + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$schema", "/$schema", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$defs", "/$defs", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$defs/inner/properties", "/$defs/inner/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, "/$defs/inner"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$defs/inner/properties/a/properties", + "/$defs/inner/properties/a/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/$defs/inner/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/$defs/inner/properties/a/properties/b/type", + "/$defs/inner/properties/a/properties/b/type", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/$defs/inner/properties/a/properties/b"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties", "/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, ""); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties", "/properties/a/properties", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, "/properties/a"); + EXPECT_ANONYMOUS_FRAME_STATIC_POINTER( + frame, "#/properties/a/properties/b/$ref", + "/properties/a/properties/b/$ref", + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", {}, + "/properties/a/properties/b"); + + // References + + EXPECT_EQ(frame.references().size(), 2); + + EXPECT_STATIC_REFERENCE( + frame, "/$schema", "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2020-12/schema", std::nullopt, + "https://json-schema.org/draft/2020-12/schema"); + EXPECT_STATIC_REFERENCE(frame, "/properties/a/properties/b/$ref", + "#/$defs/inner", std::nullopt, "/$defs/inner", + "#/$defs/inner"); +}