|
@@ -161,6 +161,8 @@ public:
|
|
|
|
|
|
|
|
ReferenceManager<A> ref(json, version, factory.keywords(version));
|
|
ReferenceManager<A> ref(json, version, factory.keywords(version));
|
|
|
detail::ParserContext<A> root{*this, json, version, factory, external, ref};
|
|
detail::ParserContext<A> root{*this, json, version, factory, external, ref};
|
|
|
|
|
+
|
|
|
|
|
+ root.where = root.dynamic_where = ref.canonicalize({}, {});
|
|
|
construct(root);
|
|
construct(root);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -229,55 +231,59 @@ private:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <Adapter A>
|
|
template <Adapter A>
|
|
|
- schema::Node const * resolve(detail::Reference ref, detail::ParserContext<A> const & context) {
|
|
|
|
|
- ref = context.ref.canonicalize(ref, context.where);
|
|
|
|
|
-
|
|
|
|
|
- // Special case if the root-level document does not have an $id property
|
|
|
|
|
- if (std::optional root = context.ref.root(ref.root())) {
|
|
|
|
|
- return fetch_schema(context.rebind(ref.pointer().walk(*root), ref));
|
|
|
|
|
|
|
+ schema::Node const * resolve(detail::Reference const & ref,
|
|
|
|
|
+ detail::ParserContext<A> const & context,
|
|
|
|
|
+ bool is_dynamic_recursion = false) {
|
|
|
|
|
+ detail::Reference lexical = context.ref.canonicalize(ref, context.where);
|
|
|
|
|
+ detail::Reference dynamic =
|
|
|
|
|
+ dynamic_anchors_.empty() || is_dynamic_recursion ? lexical : context.dynamic_where / "$ref";
|
|
|
|
|
+
|
|
|
|
|
+ if (std::optional cached = from_cache(dynamic)) {
|
|
|
|
|
+ return *cached;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (std::optional cached = from_cache(ref)) {
|
|
|
|
|
- return *cached;
|
|
|
|
|
|
|
+ // Special case if the root-level document does not have an $id property
|
|
|
|
|
+ if (std::optional root = context.ref.root(lexical.root())) {
|
|
|
|
|
+ return fetch_schema(context.rebind(lexical.pointer().walk(*root), lexical, dynamic));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- std::optional schema = context.external.try_load(ref.uri());
|
|
|
|
|
|
|
+ std::optional schema = context.external.try_load(lexical.uri());
|
|
|
if (not schema.has_value()) {
|
|
if (not schema.has_value()) {
|
|
|
- std::string error = "URIResolver could not resolve " + std::string(ref.uri());
|
|
|
|
|
- return alias(ref, &cache_.try_emplace(ref, error).first->second);
|
|
|
|
|
|
|
+ std::string error = "URIResolver could not resolve " + std::string(lexical.uri());
|
|
|
|
|
+ return alias(dynamic, &cache_.try_emplace(dynamic, error).first->second);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- context.ref.prime(*schema, ref.uri(), context.version,
|
|
|
|
|
|
|
+ context.ref.prime(*schema, lexical.uri(), context.version,
|
|
|
context.factory.keywords(context.version));
|
|
context.factory.keywords(context.version));
|
|
|
|
|
|
|
|
- if (std::optional root = context.ref.root(ref.root())) {
|
|
|
|
|
- return fetch_schema(context.rebind(ref.pointer().walk(*root), ref));
|
|
|
|
|
|
|
+ if (std::optional root = context.ref.root(lexical.root())) {
|
|
|
|
|
+ return fetch_schema(context.rebind(lexical.pointer().walk(*root), lexical, dynamic));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return fetch_schema(context.rebind(ref.pointer().walk(*schema), ref));
|
|
|
|
|
|
|
+ return fetch_schema(context.rebind(lexical.pointer().walk(*schema), lexical, dynamic));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <Adapter A> schema::Node const * fetch_schema(detail::ParserContext<A> const & context) {
|
|
template <Adapter A> schema::Node const * fetch_schema(detail::ParserContext<A> const & context) {
|
|
|
// TODO(samjaffe): No longer promises uniqueness - instead track unique URI's
|
|
// TODO(samjaffe): No longer promises uniqueness - instead track unique URI's
|
|
|
- if (std::optional cached = from_cache(context.where)) {
|
|
|
|
|
|
|
+ if (std::optional cached = from_cache(context.dynamic_where)) {
|
|
|
return *cached;
|
|
return *cached;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
adapter::Type const type = context.schema.type();
|
|
adapter::Type const type = context.schema.type();
|
|
|
if (type == adapter::Type::Boolean && context.version >= schema::Version::Draft06) {
|
|
if (type == adapter::Type::Boolean && context.version >= schema::Version::Draft06) {
|
|
|
- return alias(context.where, context.schema.as_boolean() ? &accept_ : &reject_);
|
|
|
|
|
|
|
+ return alias(context.dynamic_where, context.schema.as_boolean() ? &accept_ : &reject_);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- EXPECT_M(type == adapter::Type::Object, "invalid schema at " << context.where);
|
|
|
|
|
|
|
+ EXPECT_M(type == adapter::Type::Object, "invalid schema at " << context.dynamic_where);
|
|
|
if (context.schema.object_size() == 0) {
|
|
if (context.schema.object_size() == 0) {
|
|
|
- return alias(context.where, &accept_);
|
|
|
|
|
|
|
+ return alias(context.dynamic_where, &accept_);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- auto [it, created] = cache_.try_emplace(context.where);
|
|
|
|
|
- EXPECT_M(created, "creating duplicate schema at... " << context.where);
|
|
|
|
|
|
|
+ auto [it, created] = cache_.try_emplace(context.dynamic_where);
|
|
|
|
|
+ EXPECT_M(created, "creating duplicate schema at... " << context.dynamic_where);
|
|
|
|
|
|
|
|
// Do this here first in order to protect from infinite loops
|
|
// Do this here first in order to protect from infinite loops
|
|
|
- alias(context.where, &it->second);
|
|
|
|
|
|
|
+ alias(context.dynamic_where, &it->second);
|
|
|
it->second.construct(context);
|
|
it->second.construct(context);
|
|
|
/* if (not it->second.is_pure_reference()) { */
|
|
/* if (not it->second.is_pure_reference()) { */
|
|
|
return &it->second;
|
|
return &it->second;
|
|
@@ -311,23 +317,43 @@ namespace jvalidate::schema {
|
|
|
template <Adapter A> detail::OnBlockExit Node::resolve_anchor(detail::ParserContext<A> & context) {
|
|
template <Adapter A> detail::OnBlockExit Node::resolve_anchor(detail::ParserContext<A> & context) {
|
|
|
auto const schema = context.schema.as_object();
|
|
auto const schema = context.schema.as_object();
|
|
|
|
|
|
|
|
- if (schema.contains("$anchor")) {
|
|
|
|
|
|
|
+ if (schema.contains("$anchor") || context.version < schema::Version::Draft2019_09) {
|
|
|
return nullptr;
|
|
return nullptr;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (context.version == schema::Version::Draft2019_09) {
|
|
|
|
|
+ if (not schema.contains("$recursiveAnchor") || not schema["$recursiveAnchor"].as_boolean()) {
|
|
|
|
|
+ if (not schema.contains("$id") && not schema.contains("$recursiveAnchor")) {
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto it = context.root.dynamic_anchors_.find(detail::Anchor());
|
|
|
|
|
+ if (it == context.root.dynamic_anchors_.end()) {
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ detail::Reference where = it->second;
|
|
|
|
|
+ context.root.dynamic_anchors_.erase(it);
|
|
|
|
|
+
|
|
|
|
|
+ return [&root = context.root, where]() {
|
|
|
|
|
+ root.dynamic_anchors_.emplace(detail::Anchor(), where);
|
|
|
|
|
+ };
|
|
|
|
|
+ } else if (context.root.dynamic_anchors_.emplace(detail::Anchor(), context.where).second) {
|
|
|
|
|
+ return [&root = context.root]() { root.dynamic_anchors_.erase(detail::Anchor()); };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (context.version == schema::Version::Draft2019_09 && schema.contains("$recursiveAnchor") &&
|
|
if (context.version == schema::Version::Draft2019_09 && schema.contains("$recursiveAnchor") &&
|
|
|
schema["$recursiveAnchor"].as_boolean()) {
|
|
schema["$recursiveAnchor"].as_boolean()) {
|
|
|
- detail::Reference actual_location = context.ref.canonicalize({}, context.where);
|
|
|
|
|
- if (context.root.dynamic_anchors_.emplace(detail::Anchor(), actual_location).second) {
|
|
|
|
|
|
|
+ if (context.root.dynamic_anchors_.emplace(detail::Anchor(), context.where).second) {
|
|
|
return [&root = context.root]() { root.dynamic_anchors_.erase(detail::Anchor()); };
|
|
return [&root = context.root]() { root.dynamic_anchors_.erase(detail::Anchor()); };
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (context.version > Version::Draft2019_09 && schema.contains("$dynamicAnchor")) {
|
|
if (context.version > Version::Draft2019_09 && schema.contains("$dynamicAnchor")) {
|
|
|
detail::Anchor anchor(schema["$dynamicAnchor"].as_string());
|
|
detail::Anchor anchor(schema["$dynamicAnchor"].as_string());
|
|
|
- detail::Reference actual_location = context.ref.canonicalize({}, context.where);
|
|
|
|
|
|
|
|
|
|
- if (context.root.dynamic_anchors_.emplace(anchor, actual_location).second) {
|
|
|
|
|
|
|
+ if (context.root.dynamic_anchors_.emplace(anchor, context.where).second) {
|
|
|
return [&root = context.root, anchor]() { root.dynamic_anchors_.erase(anchor); };
|
|
return [&root = context.root, anchor]() { root.dynamic_anchors_.erase(anchor); };
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -341,7 +367,9 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
|
|
|
if (schema.contains("$ref")) {
|
|
if (schema.contains("$ref")) {
|
|
|
detail::Reference ref(schema["$ref"].as_string());
|
|
detail::Reference ref(schema["$ref"].as_string());
|
|
|
|
|
|
|
|
- reference_ = context.root.resolve(ref, context);
|
|
|
|
|
|
|
+ bool is_dynamic_ref_cludge = context.root.dynamic_anchors_.contains(ref.anchor()) &&
|
|
|
|
|
+ ref.uri().empty() && ref.pointer().empty();
|
|
|
|
|
+ reference_ = context.root.resolve(ref, context, is_dynamic_ref_cludge);
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -352,15 +380,15 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
|
|
|
std::string const dyn_ref =
|
|
std::string const dyn_ref =
|
|
|
context.version > schema::Version::Draft2019_09 ? "$dynamicRef" : "$recursiveRef";
|
|
context.version > schema::Version::Draft2019_09 ? "$dynamicRef" : "$recursiveRef";
|
|
|
if (schema.contains(dyn_ref)) {
|
|
if (schema.contains(dyn_ref)) {
|
|
|
- detail::RootReference ref(schema[dyn_ref].as_string());
|
|
|
|
|
|
|
+ detail::Reference ref(schema[dyn_ref].as_string());
|
|
|
|
|
|
|
|
// TODO(samjaffe): Relocate...
|
|
// TODO(samjaffe): Relocate...
|
|
|
if (auto it = context.root.dynamic_anchors_.find(ref.anchor());
|
|
if (auto it = context.root.dynamic_anchors_.find(ref.anchor());
|
|
|
it != context.root.dynamic_anchors_.end()) {
|
|
it != context.root.dynamic_anchors_.end()) {
|
|
|
// TODO(samjaffe): This does not re-compute things...
|
|
// TODO(samjaffe): This does not re-compute things...
|
|
|
- reference_ = context.root.resolve(it->second, context);
|
|
|
|
|
|
|
+ reference_ = context.root.resolve(it->second, context, true);
|
|
|
} else {
|
|
} else {
|
|
|
- reference_ = context.root.resolve(detail::Reference(ref), context);
|
|
|
|
|
|
|
+ reference_ = context.root.resolve(ref, context);
|
|
|
}
|
|
}
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|