55
66#include < sourcemeta/core/jsonschema.h>
77#include < sourcemeta/core/regex.h>
8+ #include < sourcemeta/core/uri.h>
89
910#include < cassert> // assert
1011#include < string_view> // std::string_view
@@ -55,9 +56,9 @@ auto handle_string(const sourcemeta::core::JSON &schema,
5556 const sourcemeta::core::SchemaResolver &,
5657 const sourcemeta::core::JSON &subschema) -> IRScalar {
5758 ONLY_WHITELIST_KEYWORDS (schema, subschema, location.pointer ,
58- {" $schema" , " $id" , " $anchor" , " $defs " , " $vocabulary " ,
59- " type " , " minLength " , " maxLength " , " pattern " ,
60- " format" });
59+ {" $schema" , " $id" , " $anchor" , " $dynamicAnchor " ,
60+ " $defs " , " $vocabulary " , " type " , " minLength " ,
61+ " maxLength " , " pattern " , " format" });
6162 return IRScalar{{.pointer = sourcemeta::core::to_pointer (location.pointer ),
6263 .symbol = symbol (frame, location)},
6364 IRScalarType::String};
@@ -71,8 +72,8 @@ auto handle_object(const sourcemeta::core::JSON &schema,
7172 const sourcemeta::core::JSON &subschema) -> IRObject {
7273 ONLY_WHITELIST_KEYWORDS (
7374 schema, subschema, location.pointer ,
74- {" $defs" , " $schema" , " $id" , " $anchor" , " $vocabulary " , " type " ,
75- " properties" , " required" ,
75+ {" $defs" , " $schema" , " $id" , " $anchor" , " $dynamicAnchor " , " $vocabulary " ,
76+ " type " , " properties" , " required" ,
7677 // Note that most programming languages CANNOT represent the idea
7778 // of additional properties, mainly if they differ from the types of the
7879 // other properties. Therefore, we whitelist this, but we consider it to
@@ -175,9 +176,10 @@ auto handle_integer(const sourcemeta::core::JSON &schema,
175176 const sourcemeta::core::SchemaResolver &,
176177 const sourcemeta::core::JSON &subschema) -> IRScalar {
177178 ONLY_WHITELIST_KEYWORDS (schema, subschema, location.pointer ,
178- {" $schema" , " $id" , " $anchor" , " $defs" , " $vocabulary" ,
179- " type" , " minimum" , " maximum" , " exclusiveMinimum" ,
180- " exclusiveMaximum" , " multipleOf" });
179+ {" $schema" , " $id" , " $anchor" , " $dynamicAnchor" ,
180+ " $defs" , " $vocabulary" , " type" , " minimum" , " maximum" ,
181+ " exclusiveMinimum" , " exclusiveMaximum" ,
182+ " multipleOf" });
181183 return IRScalar{{.pointer = sourcemeta::core::to_pointer (location.pointer ),
182184 .symbol = symbol (frame, location)},
183185 IRScalarType::Integer};
@@ -190,9 +192,10 @@ auto handle_number(const sourcemeta::core::JSON &schema,
190192 const sourcemeta::core::SchemaResolver &,
191193 const sourcemeta::core::JSON &subschema) -> IRScalar {
192194 ONLY_WHITELIST_KEYWORDS (schema, subschema, location.pointer ,
193- {" $schema" , " $id" , " $anchor" , " $defs" , " $vocabulary" ,
194- " type" , " minimum" , " maximum" , " exclusiveMinimum" ,
195- " exclusiveMaximum" , " multipleOf" });
195+ {" $schema" , " $id" , " $anchor" , " $dynamicAnchor" ,
196+ " $defs" , " $vocabulary" , " type" , " minimum" , " maximum" ,
197+ " exclusiveMinimum" , " exclusiveMaximum" ,
198+ " multipleOf" });
196199 return IRScalar{{.pointer = sourcemeta::core::to_pointer (location.pointer ),
197200 .symbol = symbol (frame, location)},
198201 IRScalarType::Number};
@@ -205,9 +208,9 @@ auto handle_array(const sourcemeta::core::JSON &schema,
205208 const sourcemeta::core::SchemaResolver &,
206209 const sourcemeta::core::JSON &subschema) -> IREntity {
207210 ONLY_WHITELIST_KEYWORDS (schema, subschema, location.pointer ,
208- {" $schema" , " $id" , " $anchor" , " $defs " , " $vocabulary " ,
209- " type " , " items " , " minItems " , " maxItems " ,
210- " uniqueItems" , " contains" , " minContains" ,
211+ {" $schema" , " $id" , " $anchor" , " $dynamicAnchor " ,
212+ " $defs " , " $vocabulary " , " type " , " items " , " minItems " ,
213+ " maxItems " , " uniqueItems" , " contains" , " minContains" ,
211214 " maxContains" , " additionalItems" , " prefixItems" });
212215
213216 using Vocabularies = sourcemeta::core::Vocabularies;
@@ -321,9 +324,9 @@ auto handle_enum(const sourcemeta::core::JSON &schema,
321324 const sourcemeta::core::Vocabularies &,
322325 const sourcemeta::core::SchemaResolver &,
323326 const sourcemeta::core::JSON &subschema) -> IREntity {
324- ONLY_WHITELIST_KEYWORDS (
325- schema, subschema, location. pointer ,
326- { " $schema " , " $id " , " $anchor " , " $defs" , " $vocabulary" , " enum" });
327+ ONLY_WHITELIST_KEYWORDS (schema, subschema, location. pointer ,
328+ { " $ schema" , " $id " , " $anchor " , " $dynamicAnchor " ,
329+ " $defs" , " $vocabulary" , " enum" });
327330 const auto &enum_json{subschema.at (" enum" )};
328331
329332 // Boolean and null special cases
@@ -357,9 +360,9 @@ auto handle_anyof(const sourcemeta::core::JSON &schema,
357360 const sourcemeta::core::Vocabularies &,
358361 const sourcemeta::core::SchemaResolver &,
359362 const sourcemeta::core::JSON &subschema) -> IREntity {
360- ONLY_WHITELIST_KEYWORDS (
361- schema, subschema, location. pointer ,
362- { " $schema " , " $id " , " $anchor " , " $defs" , " $vocabulary" , " anyOf" });
363+ ONLY_WHITELIST_KEYWORDS (schema, subschema, location. pointer ,
364+ { " $ schema" , " $id " , " $anchor " , " $dynamicAnchor " ,
365+ " $defs" , " $vocabulary" , " anyOf" });
363366
364367 const auto &any_of{subschema.at (" anyOf" )};
365368 assert (any_of.is_array ());
@@ -391,9 +394,9 @@ auto handle_oneof(const sourcemeta::core::JSON &schema,
391394 const sourcemeta::core::Vocabularies &,
392395 const sourcemeta::core::SchemaResolver &,
393396 const sourcemeta::core::JSON &subschema) -> IREntity {
394- ONLY_WHITELIST_KEYWORDS (
395- schema, subschema, location. pointer ,
396- { " $schema " , " $id " , " $anchor " , " $defs" , " $vocabulary" , " oneOf" });
397+ ONLY_WHITELIST_KEYWORDS (schema, subschema, location. pointer ,
398+ { " $ schema" , " $id " , " $anchor " , " $dynamicAnchor " ,
399+ " $defs" , " $vocabulary" , " oneOf" });
397400
398401 const auto &one_of{subschema.at (" oneOf" )};
399402 assert (one_of.is_array ());
@@ -425,9 +428,9 @@ auto handle_ref(const sourcemeta::core::JSON &schema,
425428 const sourcemeta::core::Vocabularies &,
426429 const sourcemeta::core::SchemaResolver &,
427430 const sourcemeta::core::JSON &subschema) -> IREntity {
428- ONLY_WHITELIST_KEYWORDS (
429- schema, subschema, location. pointer ,
430- { " $schema " , " $id " , " $anchor " , " $defs" , " $vocabulary" , " $ref" });
431+ ONLY_WHITELIST_KEYWORDS (schema, subschema, location. pointer ,
432+ { " $ schema" , " $id " , " $anchor " , " $dynamicAnchor " ,
433+ " $defs" , " $vocabulary" , " $ref" });
431434
432435 auto ref_pointer{sourcemeta::core::to_pointer (location.pointer )};
433436 ref_pointer.push_back (" $ref" );
@@ -454,6 +457,74 @@ auto handle_ref(const sourcemeta::core::JSON &schema,
454457 .symbol = symbol (frame, target_location)}};
455458}
456459
460+ auto handle_dynamic_ref (const sourcemeta::core::JSON &schema,
461+ const sourcemeta::core::SchemaFrame &frame,
462+ const sourcemeta::core::SchemaFrame::Location &location,
463+ const sourcemeta::core::Vocabularies &,
464+ const sourcemeta::core::SchemaResolver &,
465+ const sourcemeta::core::JSON &subschema) -> IREntity {
466+ ONLY_WHITELIST_KEYWORDS (schema, subschema, location.pointer ,
467+ {" $schema" , " $id" , " $anchor" , " $dynamicAnchor" ,
468+ " $defs" , " $vocabulary" , " $dynamicRef" });
469+
470+ auto ref_pointer{sourcemeta::core::to_pointer (location.pointer )};
471+ ref_pointer.push_back (" $dynamicRef" );
472+ const auto ref_weak_pointer{sourcemeta::core::to_weak_pointer (ref_pointer)};
473+
474+ const auto &references{frame.references ()};
475+
476+ // Note: The frame internally converts single-target dynamic references to
477+ // static reference
478+ const auto static_reference{references.find (
479+ {sourcemeta::core::SchemaReferenceType::Static, ref_weak_pointer})};
480+ if (static_reference != references.cend ()) {
481+ const auto &destination{static_reference->second .destination };
482+ const auto target{frame.traverse (destination)};
483+ if (!target.has_value ()) {
484+ throw UnexpectedSchemaError (schema, location.pointer ,
485+ " Could not resolve reference destination" );
486+ }
487+
488+ const auto &target_location{target.value ().get ()};
489+
490+ return IRReference{
491+ {.pointer = sourcemeta::core::to_pointer (location.pointer ),
492+ .symbol = symbol (frame, location)},
493+ {.pointer = sourcemeta::core::to_pointer (target_location.pointer ),
494+ .symbol = symbol (frame, target_location)}};
495+ }
496+
497+ // Multi-target dynamic reference: find all dynamic anchors with the matching
498+ // fragment and emit a union of all possible targets
499+ const auto dynamic_reference{references.find (
500+ {sourcemeta::core::SchemaReferenceType::Dynamic, ref_weak_pointer})};
501+ assert (dynamic_reference != references.cend ());
502+ assert (dynamic_reference->second .fragment .has_value ());
503+ const auto &fragment{dynamic_reference->second .fragment .value ()};
504+
505+ std::vector<IRType> branches;
506+ for (const auto &[key, entry] : frame.locations ()) {
507+ if (key.first != sourcemeta::core::SchemaReferenceType::Dynamic ||
508+ entry.type != sourcemeta::core::SchemaFrame::LocationType::Anchor) {
509+ continue ;
510+ }
511+
512+ const sourcemeta::core::URI anchor_uri{key.second };
513+ const auto anchor_fragment{anchor_uri.fragment ()};
514+ if (!anchor_fragment.has_value () || anchor_fragment.value () != fragment) {
515+ continue ;
516+ }
517+
518+ branches.push_back ({.pointer = sourcemeta::core::to_pointer (entry.pointer ),
519+ .symbol = symbol (frame, entry)});
520+ }
521+
522+ assert (!branches.empty ());
523+ return IRUnion{{.pointer = sourcemeta::core::to_pointer (location.pointer ),
524+ .symbol = symbol (frame, location)},
525+ std::move (branches)};
526+ }
527+
457528auto default_compiler (const sourcemeta::core::JSON &schema,
458529 const sourcemeta::core::SchemaFrame &frame,
459530 const sourcemeta::core::SchemaFrame::Location &location,
@@ -536,6 +607,9 @@ auto default_compiler(const sourcemeta::core::JSON &schema,
536607 } else if (subschema.defines (" oneOf" )) {
537608 return handle_oneof (schema, frame, location, vocabularies, resolver,
538609 subschema);
610+ } else if (subschema.defines (" $dynamicRef" )) {
611+ return handle_dynamic_ref (schema, frame, location, vocabularies, resolver,
612+ subschema);
539613 } else if (subschema.defines (" $ref" )) {
540614 return handle_ref (schema, frame, location, vocabularies, resolver,
541615 subschema);
0 commit comments