Explorar o código

refactor: allow specifying format-as-assertion on the constraint-level

Sam Jaffe hai 1 ano
pai
achega
25699acb11

+ 2 - 1
include/jvalidate/constraint.h

@@ -362,7 +362,8 @@ public:
   }
 
   static auto format(detail::ParserContext<A> const & context) {
-    return std::make_unique<constraint::FormatConstraint>(context.schema.as_string());
+    return std::make_unique<constraint::FormatConstraint>(context.schema.as_string(),
+                                                          context.vocab->is_format_assertion());
   }
 
   // SECTION: Array Constraints

+ 3 - 1
include/jvalidate/constraint/string_constraint.h

@@ -34,8 +34,10 @@ public:
 class FormatConstraint : public SimpleConstraint<FormatConstraint> {
 public:
   std::string format;
+  bool is_assertion;
 
 public:
-  FormatConstraint(std::string const & format) : format(format) {}
+  FormatConstraint(std::string const & format, bool is_assertion)
+      : format(format), is_assertion(is_assertion) {}
 };
 }

+ 9 - 4
include/jvalidate/detail/reference_manager.h

@@ -82,7 +82,8 @@ public:
     parent = vocab(URI(metaschema["$schema"].as_string()));
 
     if (metaschema.contains("$vocabulary")) {
-      parent.restrict(extract_keywords(metaschema["$vocabulary"].as_object()));
+      auto [keywords, vocabularies] = extract_keywords(metaschema["$vocabulary"].as_object());
+      parent.restrict(keywords, vocabularies);
     }
 
     return parent;
@@ -280,20 +281,24 @@ private:
     }
   }
 
-  std::unordered_set<std::string> extract_keywords(ObjectAdapter auto const & vocabularies) const {
+  auto extract_keywords(ObjectAdapter auto const & vocabularies) const
+      -> std::pair<std::unordered_set<std::string>, std::unordered_set<std::string>> {
     std::unordered_set<std::string> keywords;
+    std::unordered_set<std::string> vocab_docs;
     for (auto [vocab, enabled] : vocabularies) {
       if (not enabled.as_boolean()) {
         continue;
       }
 
-      vocab.replace(vocab.find("/vocab/"), 7, "/meta/");
+      size_t n = vocab.find("/vocab/");
+      vocab_docs.emplace(vocab.substr(n));
+      vocab.replace(n, 7, "/meta/");
       auto vocab_object = external_.try_load(URI(vocab));
       for (auto const & [keyword, _] : vocab_object->as_object()["properties"].as_object()) {
         keywords.insert(keyword);
       }
     }
-    return keywords;
+    return std::make_pair(keywords, vocab_docs);
   }
 };
 }

+ 28 - 1
include/jvalidate/detail/vocabulary.h

@@ -19,6 +19,7 @@ private:
   schema::Version version_;
   std::unordered_map<std::string_view, MakeConstraint> make_;
   std::unordered_set<std::string_view> permitted_;
+  std::unordered_set<std::string> vocabularies_;
 
   // TODO(samjaffe): Migrate this back to constraintsfactory
   std::unordered_set<std::string_view> keywords_{"$defs",
@@ -56,8 +57,10 @@ public:
     }
   }
 
-  void restrict(std::unordered_set<std::string> const & permitted_keywords) & {
+  void restrict(std::unordered_set<std::string> const & permitted_keywords,
+                std::unordered_set<std::string> const & vocabularies) & {
     permitted_.clear();
+    vocabularies_ = vocabularies;
     for (auto const & [keyword, _] : make_) {
       if (permitted_keywords.contains(std::string(keyword))) {
         permitted_.insert(keyword);
@@ -67,6 +70,30 @@ public:
 
   schema::Version version() const { return version_; }
 
+  bool is_format_assertion() const {
+    // In Draft07 and prior - format assertions were considered enabled by
+    // default. This is - of course - problematic because very few
+    // implementations actually had full support for format constraints.
+    if (version_ < schema::Version::Draft2019_09) {
+      return true;
+    }
+
+    // Some implementations wouldn't even bother with format constraints, and
+    // others would provide implementations that either missed a number of edge
+    // cases or were flat-out wrong on certail matters.
+    // Therefore - starting in Draft 2019-09, the format keyword is an
+    // annotation by default, instead of an assertion.
+    if (version_ == schema::Version::Draft2019_09) {
+      return permitted_.contains("/vocab/format");
+    }
+
+    // Draft 2020-12 makes this even more explicit - having separate vocabulary
+    // documents for "format as assertion" and "format as annotation". Allowing
+    // validators to add format constraints that are only used for annotating
+    // results.
+    return vocabularies_.contains("/vocab/format-assertion");
+  }
+
   /**
    * @brief Is the given "key"word actually a keyword? As in, would
    * I expect to resolve a constraint out of it.

+ 1 - 1
include/jvalidate/validation_visitor.h

@@ -235,7 +235,7 @@ public:
     // https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-defined-formats
     NOOP_UNLESS_TYPE(String);
 
-    if (not cfg_.validate_format) {
+    if (not cfg_.validate_format && not cons.is_assertion) {
       return true;
     }