|
|
@@ -1,6 +1,7 @@
|
|
|
#pragma once
|
|
|
|
|
|
#include <memory>
|
|
|
+#include <type_traits>
|
|
|
#include <unordered_map>
|
|
|
#include <vector>
|
|
|
|
|
|
@@ -12,6 +13,7 @@
|
|
|
#include <jvalidate/detail/parser_context.h>
|
|
|
#include <jvalidate/detail/pointer.h>
|
|
|
#include <jvalidate/detail/reference.h>
|
|
|
+#include <jvalidate/document_cache.h>
|
|
|
#include <jvalidate/enum.h>
|
|
|
#include <jvalidate/forward.h>
|
|
|
|
|
|
@@ -118,15 +120,82 @@ private:
|
|
|
std::map<detail::Reference, schema::Node const *> alias_cache_;
|
|
|
|
|
|
public:
|
|
|
+ /**
|
|
|
+ * @brief Construct a new schema. All other constructors of this type may be
|
|
|
+ * considered syntactic sugar for this constructor.
|
|
|
+ *
|
|
|
+ * As such, the true signature of this class's contructor is:
|
|
|
+ *
|
|
|
+ * Schema(Adapter | JSON [, schema::Version]
|
|
|
+ * [, URIResolver & | URIResolver &&]
|
|
|
+ * [, ConstraintFactory<A> const &])
|
|
|
+ *
|
|
|
+ * as long as the order of arguments is preserved - the constructor will work
|
|
|
+ * no matter which arguments are ignored. The only required argument being
|
|
|
+ * the JSON object/Adapter.
|
|
|
+ *
|
|
|
+ * @param json An adapter to a json object
|
|
|
+ *
|
|
|
+ * @param version The json-schema draft version that all schemas will prefer
|
|
|
+ *
|
|
|
+ * @param external An object capable of resolving URIs, and turning them into
|
|
|
+ * Adapter objects. Holds a cache and so must be mutable.
|
|
|
+ *
|
|
|
+ * @param factory An object that manuafactures constraints - allows the user
|
|
|
+ * to provide custom extensions or even modify the behavior of existing
|
|
|
+ * keywords by overridding the virtual accessor function(s).
|
|
|
+ */
|
|
|
template <Adapter A>
|
|
|
- explicit Schema(A const & json, ConstraintFactory<A> const & factory = {})
|
|
|
- : Schema(json, schema_version(json), factory) {}
|
|
|
-
|
|
|
- template <Adapter A>
|
|
|
- Schema(A const & json, schema::Version version, ConstraintFactory<A> const & factory = {})
|
|
|
- : schema::Node(detail::ParserContext<A>{
|
|
|
- .root = *this, .schema = json, .version = version, .factory = factory}) {}
|
|
|
-
|
|
|
+ Schema(A const & json, schema::Version version, DocumentCache<A> & external,
|
|
|
+ ConstraintFactory<A> const & factory = {})
|
|
|
+ : schema::Node(detail::ParserContext<A>{*this, json, version, factory, external}) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param json An adapter to a json schema
|
|
|
+ *
|
|
|
+ * @param version The json-schema draft version that all schemas will prefer
|
|
|
+ *
|
|
|
+ * @param external An object capable of resolving URIs, and turning them into
|
|
|
+ * Adapter objects. Holds a cache and so must be mutable. If this constructor
|
|
|
+ * is called, then it means that the cache is a one-off object, and will not
|
|
|
+ * be reused.
|
|
|
+ */
|
|
|
+ template <Adapter A, typename... Args>
|
|
|
+ Schema(A const & json, schema::Version version, DocumentCache<A> && external, Args &&... args)
|
|
|
+ : Schema(json, version, external, std::forward<Args>(args)...) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param json An adapter to a json schema
|
|
|
+ *
|
|
|
+ * @param version The json-schema draft version that all schemas will prefer
|
|
|
+ *
|
|
|
+ * @param resolve A function capable of resolving URIs, and storing the
|
|
|
+ * contents in a provided concrete JSON object.
|
|
|
+ */
|
|
|
+ template <Adapter A, typename... Args>
|
|
|
+ Schema(A const & json, schema::Version version, URIResolver<A> resolve, Args &&... args)
|
|
|
+ : Schema(json, version, DocumentCache<A>(resolve), std::forward<Args>(args)...) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param json An adapter to a json schema
|
|
|
+ *
|
|
|
+ * @param version The json-schema draft version that all schemas will prefer
|
|
|
+ */
|
|
|
+ template <Adapter A, Not<DocumentCache<A>>... Args>
|
|
|
+ Schema(A const & json, schema::Version version, Args &&... args)
|
|
|
+ : Schema(json, version, DocumentCache<A>(), std::forward<Args>(args)...) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param json An adapter to a json schema
|
|
|
+ */
|
|
|
+ template <Adapter A, Not<schema::Version>... Args>
|
|
|
+ explicit Schema(A const & json, Args &&... args)
|
|
|
+ : Schema(json, schema_version(json), std::forward<Args>(args)...) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param json Any non-adapter (JSON) object. Will be immedately converted
|
|
|
+ * into an Adapter object to allow us to walk through it w/o specialization.
|
|
|
+ */
|
|
|
template <typename JSON, typename... Args>
|
|
|
explicit Schema(JSON const & json, Args &&... args)
|
|
|
: Schema(adapter::AdapterFor<JSON const>(json), std::forward<Args>(args)...) {}
|
|
|
@@ -156,22 +225,48 @@ private:
|
|
|
return schema;
|
|
|
}
|
|
|
|
|
|
+ std::optional<schema::Node const *> from_cache(detail::Reference ref) {
|
|
|
+ if (auto it = anchors_.find(ref.root()); it != anchors_.end()) {
|
|
|
+ ref = it->second / ref.pointer();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (auto it = alias_cache_.find(ref); it != alias_cache_.end()) {
|
|
|
+ return it->second;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::nullopt;
|
|
|
+ }
|
|
|
+
|
|
|
template <Adapter A>
|
|
|
- schema::Node const * resolve(detail::Reference ref, detail::Reference const & from,
|
|
|
- schema::Version default_version) {
|
|
|
+ schema::Node const * resolve(detail::Reference ref, detail::ParserContext<A> const & context) {
|
|
|
// Special case if the root-level document does not have an $id property
|
|
|
- if (ref == detail::Reference() && ref.anchor() == from.anchor()) {
|
|
|
+ if (ref == detail::Reference() && ref.anchor() == context.where.anchor()) {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
- if (auto it = anchors_.find(ref.root()); it != anchors_.end()) {
|
|
|
- ref = it->second / ref.pointer();
|
|
|
+ if (std::optional cached = from_cache(ref)) {
|
|
|
+ return *cached;
|
|
|
}
|
|
|
|
|
|
- if (auto it = alias_cache_.find(ref); it != alias_cache_.end()) {
|
|
|
- return it->second;
|
|
|
+ // SPECIAL RULE: Resolve this URI into the context of the calling URI
|
|
|
+ if (ref.uri().scheme().empty()) {
|
|
|
+ URI const & relative_to = context.where.uri();
|
|
|
+ EXPECT_M(relative_to.resource().rfind('/') != std::string::npos,
|
|
|
+ "Relative URIs require that the current context has a resolved URI");
|
|
|
+ ref = detail::Reference(relative_to.parent() / ref.uri(), ref.anchor(), ref.pointer());
|
|
|
}
|
|
|
- throw;
|
|
|
+
|
|
|
+ EXPECT_M(context.external, "Unable to resolve external reference(s) without a URIResolver");
|
|
|
+
|
|
|
+ std::optional schema = context.external.try_load(ref.uri());
|
|
|
+ EXPECT_M(schema.has_value(), "URIResolver could not resolve " << ref.uri());
|
|
|
+
|
|
|
+ (void)fetch_schema(context.rebind(*schema, ref.uri()));
|
|
|
+ std::optional referenced_node = from_cache(ref);
|
|
|
+ EXPECT_M(referenced_node.has_value(),
|
|
|
+ "Could not locate reference '" << ref << "' within external schema.");
|
|
|
+
|
|
|
+ return *referenced_node;
|
|
|
}
|
|
|
|
|
|
schema::Node const * resolve_dynamic(detail::Anchor const & ref) {
|
|
|
@@ -254,7 +349,7 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
|
|
|
if (schema.contains("$ref")) {
|
|
|
detail::Reference ref(schema["$ref"].as_string());
|
|
|
|
|
|
- reference_ = context.root.template resolve<A>(ref, context.where, context.version);
|
|
|
+ reference_ = context.root.resolve(ref, context);
|
|
|
return true;
|
|
|
}
|
|
|
|