#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace jvalidate { template class ReferenceManager { public: using Keywords = std::unordered_map>; private: std::map roots_; detail::ReferenceCache references_; std::multimap dynamic_anchors_; public: ReferenceManager(A const & root, schema::Version version, Keywords const & keywords) : roots_{{{}, root}} { prime_impl(root, {}, version, keywords); } bool has_dynamic_anchor(detail::RootReference const & root) const { for (auto [it, end] = dynamic_anchors_.equal_range(root.uri()); it != end; ++it) { if (it->second == root.anchor()) { return true; } } return false; } std::optional root(detail::RootReference const & root) const { if (auto it = roots_.find(root); it != roots_.end()) { return it->second; } return std::nullopt; } detail::Reference canonicalize(detail::Reference const & ref, detail::Reference const & parent) const { // Relative URI, not in the HEREDOC (or we set an $id) if (ref.uri().empty() and ref.anchor().empty()) { return detail::Reference(references_.relative_to_nearest_anchor(parent).root(), ref.pointer()); } URI uri = [this, &ref, &parent]() { if (ref.uri().empty() && parent.uri().empty()) { return references_.actual_parent_uri(parent); } URI uri = ref.uri().empty() ? parent.uri() : ref.uri(); if (not uri.is_rootless()) { return uri; } URI base = references_.actual_parent_uri(parent); EXPECT_M(base.resource().rfind('/') != std::string::npos, "Unable to deduce root for relative uri " << uri << " (" << base << ")"); return (uri.is_relative() ? base.parent() : base.root()) / uri; }(); detail::Reference rval(uri, ref.anchor(), ref.pointer()); // Will now need to go make an external fetch... // TODO(samjaffe): Make that process internal? return rval; } void prime(Adapter auto const & json, URI const & where, schema::Version version, Keywords const & keywords) { references_.emplace(where); prime_impl(json, detail::Reference(where), version, keywords); } private: void prime_impl(Adapter auto const & json, detail::Reference where, schema::Version version, Keywords const & keywords) { if (json.type() != adapter::Type::Object) { return; } canonicalize(where, version, json); for (auto const & [key, value] : json.as_object()) { auto vit = keywords.find(key); if (vit == keywords.end()) { continue; } if (vit->second.contains(schema::Wraps::Array) && value.type() == adapter::Type::Array) { size_t index = 0; for (auto const & elem : value.as_array()) { prime_impl(elem, where / key / index, version, keywords); ++index; } } else if (vit->second.contains(schema::Wraps::Object) && value.type() == adapter::Type::Object) { for (auto const & [prop, elem] : value.as_object()) { prime_impl(elem, where / key / prop, version, keywords); } } else if (vit->second.contains(schema::Wraps::Schema)) { prime_impl(value, where / key, version, keywords); } } } void canonicalize(detail::Reference & where, schema::Version version, A const & json) { std::string const id = version <= schema::Version::Draft04 ? "id" : "$id"; auto const schema = json.as_object(); detail::RootReference root = where.root(); if (schema.contains(id)) { root = detail::RootReference(schema[id].as_string()); if (root.uri().empty()) { root = detail::RootReference(where.uri(), root.anchor()); } else if (not root.uri().is_rootless() || where.uri().empty()) { // By definition - rootless URIs cannot be relative } else if (root.uri().is_relative()) { root = detail::RootReference(where.uri().parent() / root.uri(), root.anchor()); } else { root = detail::RootReference(where.uri().root() / root.uri(), root.anchor()); } roots_.emplace(root, json); where = references_.emplace(where, root); } // $anchor and its related keywords were introduced in Draft 2019-09 if (version < schema::Version::Draft2019_09) { return; } if (schema.contains("$anchor")) { root = detail::RootReference(root.uri(), detail::Anchor(schema["$anchor"].as_string())); roots_.emplace(root, json); where = references_.emplace(where, root); } if (version == schema::Version::Draft2019_09 && schema.contains("$recursiveAnchor") && schema["$recursiveAnchor"].as_boolean()) { detail::Anchor anchor; root = detail::RootReference(root.uri(), anchor); roots_.emplace(root, json); where = references_.emplace(where, root); dynamic_anchors_.emplace(root.uri(), anchor); } if (schema.contains("$dynamicAnchor") && version > schema::Version::Draft2019_09) { detail::Anchor anchor(schema["$dynamicAnchor"].as_string()); root = detail::RootReference(root.uri(), anchor); roots_.emplace(root, json); where = references_.emplace(where, root); dynamic_anchors_.emplace(root.uri(), anchor); } } }; }