Forráskód Böngészése

feat: implement draft 3 support

Sam Jaffe 1 éve
szülő
commit
6272eefee0

+ 98 - 2
include/jvalidate/constraint.h

@@ -67,12 +67,16 @@ private:
       {"dependencies", &Self::dependencies},
       {"dependentRequired", {schema::Version::Draft2019_09, &Self::dependentRequired}},
       {"dependentSchemas", {schema::Version::Draft2019_09, &Self::dependentSchemas}},
+      {"disallow",
+       {{schema::Version::Earliest, &Self::disallowDraft3}, {schema::Version::Draft04, Removed}}},
       {"divisibleBy",
        {{schema::Version::Earliest, &Self::multipleOf}, {schema::Version::Draft04, Removed}}},
       {"else", {{schema::Version::Draft07, Keyword}}},
       {"enum", &Self::isInEnumuration},
       {"exclusiveMaximum", {schema::Version::Draft06, &Self::exclusiveMaximum}},
       {"exclusiveMinimum", {schema::Version::Draft06, &Self::exclusiveMinimum}},
+      {"extends",
+       {{schema::Version::Earliest, &Self::extendsDraft3}, {schema::Version::Draft04, Removed}}},
       {"format", &Self::format},
       {"if", {schema::Version::Draft07, &Self::ifThenElse}},
       {"items",
@@ -92,11 +96,14 @@ private:
       {"pattern", &Self::pattern},
       {"patternProperties", &Self::patternProperties},
       {"prefixItems", {schema::Version::Draft2020_12, &Self::prefixItems}},
-      {"properties", &Self::properties},
+      {"properties",
+       {{schema::Version::Earliest, &Self::propertiesDraft3},
+        {schema::Version::Draft04, &Self::properties}}},
       {"propertyNames", {schema::Version::Draft06, &Self::propertyNames}},
       {"required", {schema::Version::Draft04, &Self::required}},
       {"then", {schema::Version::Draft07, Keyword}},
-      {"type", &Self::type},
+      {"type",
+       {{schema::Version::Earliest, &Self::typeDraft3}, {schema::Version::Draft04, &Self::type}}},
       {"unevaluatedItems", {schema::Version::Draft2019_09, &Self::unevaluatedItems}},
       {"unevaluatedProperties", {schema::Version::Draft2019_09, &Self::unevaluatedProperties}},
       {"uniqueItems", &Self::uniqueItems},
@@ -158,6 +165,73 @@ public:
     return std::make_unique<constraint::TypeConstraint>(types);
   }
 
+  static pConstraint typeDraft3(detail::ParserContext<A> const & context) {
+    static std::unordered_map<std::string_view, adapter::Type> const s_type_names{
+        {"null", adapter::Type::Null},       {"boolean", adapter::Type::Boolean},
+        {"integer", adapter::Type::Integer}, {"number", adapter::Type::Number},
+        {"string", adapter::Type::String},   {"array", adapter::Type::Array},
+        {"object", adapter::Type::Object},
+    };
+
+    auto to_type = [](std::string_view type) {
+      EXPECT_M(s_type_names.contains(type), "Unknown type " << type);
+      return s_type_names.at(type);
+    };
+
+    adapter::Type const type = context.schema.type();
+    if (type == adapter::Type::String) {
+      if (context.schema.as_string() == "any") {
+        return nullptr;
+      }
+      return std::make_unique<constraint::TypeConstraint>(to_type(context.schema.as_string()));
+    }
+
+    EXPECT(type == adapter::Type::Array);
+    std::vector<schema::Node const *> children;
+
+    std::set<adapter::Type> types;
+    size_t index = 0;
+    for (auto subschema : context.schema.as_array()) {
+      if (subschema.type() != adapter::Type::String) {
+        children.push_back(context.child(subschema, index).node());
+      } else if (subschema.as_string() == "any") {
+        return nullptr;
+      } else {
+        types.insert(to_type(subschema.as_string()));
+      }
+      ++index;
+    }
+
+    return constraint::PolyConstraint::AnyOf(
+        std::make_unique<constraint::TypeConstraint>(types),
+        std::make_unique<constraint::AnyOfConstraint>(children));
+  }
+
+  static pConstraint disallowDraft3(detail::ParserContext<A> const & context) {
+    return constraint::PolyConstraint::Not(typeDraft3(context));
+  }
+
+  static pConstraint extendsDraft3(detail::ParserContext<A> const & context) {
+    std::vector<schema::Node const *> children;
+    switch (context.schema.type()) {
+    case adapter::Type::Object:
+      children.push_back(context.node());
+      break;
+    case adapter::Type::Array: {
+      size_t index = 0;
+      for (auto const & subschema : context.schema.as_array()) {
+        children.push_back(context.child(subschema, index).node());
+        ++index;
+      }
+      break;
+    }
+    default:
+      JVALIDATE_THROW(std::runtime_error, "extends must be a schema of array-of-schemas");
+    }
+
+    return std::make_unique<constraint::AllOfConstraint>(children);
+  }
+
   static pConstraint ifThenElse(detail::ParserContext<A> const & context) {
     schema::Node const * then_ = context.fixed_schema(true);
     if (context.parent->contains("then")) {
@@ -431,6 +505,25 @@ public:
     return std::make_unique<constraint::PropertiesConstraint>(rval);
   }
 
+  static pConstraint propertiesDraft3(detail::ParserContext<A> const & context) {
+    EXPECT(context.schema.type() == adapter::Type::Object);
+
+    std::unordered_set<std::string> required;
+    for (auto [prop, subschema] : context.schema.as_object()) {
+      EXPECT(subschema.type() == adapter::Type::Object);
+      if (auto sub = subschema.as_object();
+          sub.contains("required") && sub["required"].as_boolean()) {
+        required.insert(prop);
+      }
+    }
+
+    if (required.empty()) {
+      return properties(context);
+    }
+    return constraint::PolyConstraint::AllOf(
+        properties(context), std::make_unique<constraint::RequiredConstraint>(std::move(required)));
+  }
+
   static auto propertyNames(detail::ParserContext<A> const & context) {
     return std::make_unique<constraint::PropertyNamesConstraint>(context.node());
   }
@@ -475,6 +568,9 @@ public:
           EXPECT(key.type() == adapter::Type::String);
           required[prop].insert(key.as_string());
         }
+      } else if (context.vocab->version() <= schema::Version::Draft03 &&
+                 subschema.type() == adapter::Type::String) {
+        required[prop].insert(subschema.as_string());
       } else {
         schemas.emplace(prop, context.child(subschema, prop).node());
       }

+ 41 - 0
include/jvalidate/constraint/general_constraint.h

@@ -2,12 +2,53 @@
 
 #include <memory>
 #include <set>
+#include <utility>
 #include <vector>
 
 #include <jvalidate/constraint/constraint.h>
 #include <jvalidate/forward.h>
+#include <jvalidate/status.h>
 
 namespace jvalidate::constraint {
+class PolyConstraint : public Constraint {
+private:
+  std::vector<std::unique_ptr<Constraint>> children_;
+  bool match_all_;
+  bool invert_{false};
+
+public:
+  template <typename... Cs> static auto AllOf(Cs &&... cs) {
+    return std::make_unique<PolyConstraint>(PolyConstraint(true, false, std::forward<Cs>(cs)...));
+  }
+
+  template <typename... Cs> static auto AnyOf(Cs &&... cs) {
+    return std::make_unique<PolyConstraint>(PolyConstraint(false, false, std::forward<Cs>(cs)...));
+  }
+
+  static auto Not(std::unique_ptr<Constraint> child) {
+    return std::make_unique<PolyConstraint>(PolyConstraint(false, true, std::move(child)));
+  }
+
+  Status accept(ConstraintVisitor const & visitor) const final {
+    Status rval = Status::Noop;
+    for (auto const & child : children_) {
+      if (match_all_) {
+        rval &= child->accept(visitor);
+      } else {
+        rval |= child->accept(visitor);
+      }
+    }
+    return invert_ ? !rval : rval;
+  }
+
+private:
+  template <typename... Cs>
+  PolyConstraint(bool match_all, bool invert, Cs &&... cs)
+      : match_all_(match_all), invert_(invert) {
+    (children_.push_back(std::forward<Cs>(cs)), ...);
+  }
+};
+
 class AllOfConstraint : public SimpleConstraint<AllOfConstraint> {
 public:
   std::vector<schema::Node const *> children;

+ 2 - 0
include/jvalidate/detail/iostream.h

@@ -31,6 +31,8 @@ inline std::ostream & operator<<(std::ostream & os, Type type) {
 namespace jvalidate::schema {
 inline std::ostream & operator<<(std::ostream & os, Version version) {
   switch (version) {
+  case Version::Draft03:
+    return os << "draft3";
   case Version::Draft04:
     return os << "draft4";
   case Version::Draft06:

+ 1 - 0
include/jvalidate/detail/reference_manager.h

@@ -28,6 +28,7 @@ public:
 
 private:
   static inline std::map<std::string_view, schema::Version> const g_schema_ids{
+      {"json-schema.org/draft-03/schema", schema::Version::Draft03},
       {"json-schema.org/draft-04/schema", schema::Version::Draft04},
       {"json-schema.org/draft-06/schema", schema::Version::Draft06},
       {"json-schema.org/draft-07/schema", schema::Version::Draft07},

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

@@ -30,6 +30,7 @@ private:
                                                  "dependencies",
                                                  "dependentSchemas",
                                                  "else",
+                                                 "extends",
                                                  "if",
                                                  "items",
                                                  "not",

+ 2 - 2
include/jvalidate/enum.h

@@ -20,7 +20,7 @@ enum class Version : int {
   //           required, dependencies, minimum, maximum, exclusiveMinimum, exclusiveMaximum,
   //           minItems, maxItems, uniqueItems, pattern, minLength, maxLength, enum, default,
   //           title, description, format, divisibleBy, disallow, extends, id, $ref, $schema
-  /* Draft03, */
+  Draft03,
 
   // New: allOf, anyOf, oneOf, not, minProperties, maxProperties, definitions
   // Changed: required, dependencies
@@ -58,7 +58,7 @@ enum class Version : int {
   // https://json-schema.org/draft/2020-12/schema
   Draft2020_12,
 
-  Earliest = Draft04,
+  Earliest = Draft03,
   Latest = Draft2020_12,
 };
 

+ 1 - 0
tests/selfvalidate_test.cxx

@@ -165,6 +165,7 @@ TEST_P(JsonSchema, TestSuite) {
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(Draft3, JsonSchema, SchemaTests(Version::Draft03), SchemaTestName);
 INSTANTIATE_TEST_SUITE_P(Draft4, JsonSchema, SchemaTests(Version::Draft04), SchemaTestName);
 INSTANTIATE_TEST_SUITE_P(Draft6, JsonSchema, SchemaTests(Version::Draft06), SchemaTestName);
 INSTANTIATE_TEST_SUITE_P(Draft7, JsonSchema, SchemaTests(Version::Draft07), SchemaTestName);