Parcourir la source

feat: implement equality for Adapters

Sam Jaffe il y a 1 an
Parent
commit
c2cb73d94f

+ 5 - 0
include/jvalidate/adapter.h

@@ -24,8 +24,13 @@ public:
   virtual double as_number() const = 0;
   virtual std::string as_string() const = 0;
 
+  virtual size_t array_size() const = 0;
+  virtual size_t object_size() const = 0;
+
   virtual Status apply_array(AdapterCallback const & cb) const = 0;
   virtual Status apply_object(ObjectAdapterCallback const & cb) const = 0;
+
+  virtual bool equals(Adapter const & lhs, bool strict) const = 0;
 };
 
 class Const {

+ 3 - 0
include/jvalidate/adapters/jsoncpp.h

@@ -1,10 +1,13 @@
 #pragma once
+#include <compare>
 #include <memory>
 #include <type_traits>
 
 #include <json/value.h>
 
+#include <jvalidate/adapter.h>
 #include <jvalidate/detail/simple_adapter.h>
+#include <jvalidate/enum.h>
 
 namespace jvalidate::adapter {
 template <typename JSON> class JsonCppAdapter;

+ 69 - 2
include/jvalidate/detail/simple_adapter.h

@@ -93,7 +93,7 @@ public:
   SimpleAdapter(JSON * value = nullptr) : value_(value) {}
   SimpleAdapter(JSON & value) : value_(&value) {}
 
-  size_t array_size() const { return self().as_array().size(); }
+  size_t array_size() const final { return self().as_array().size(); }
 
   CRTP operator[](size_t index) const { return self().as_array()[index]; }
 
@@ -107,7 +107,7 @@ public:
     return result;
   }
 
-  size_t object_size() const { return self().as_object().size(); }
+  size_t object_size() const final { return self().as_object().size(); }
 
   bool contains(std::string const & key) const { return self().as_object().contains(key); }
 
@@ -127,6 +127,73 @@ public:
     return std::make_unique<GenericConst<value_type>>(const_value());
   }
 
+  template <typename Other>
+  requires std::totally_ordered_with<JSON, typename Other::value_type> std::strong_ordering
+  operator<=>(SimpleAdapter const & rhs) const {
+    using ord = std::strong_ordering;
+    if (value_ == rhs.value_) {
+      return std::strong_ordering::equal;
+    }
+    if (value_ && rhs.value_) {
+      return *value_ <=> *rhs.value_;
+    }
+    if (value_) {
+      return type() == Type::Null ? ord::equivalent : ord::greater;
+    }
+    return rhs.type() == Type::Null ? ord::equivalent : ord::less;
+  }
+
+  bool equals(Adapter const & rhs, bool strict = false) const final {
+    Type const rhs_type = rhs.type();
+    if (strict && rhs_type != type()) {
+      return false;
+    }
+
+    // TODO(samjaffe): Needs type coercion
+    switch (rhs_type) {
+    case Type::Null:
+      return const_value().isNull();
+    case Type::Boolean:
+      return rhs.as_boolean() == as_boolean();
+    case Type::Integer:
+      return rhs.as_integer() == as_integer();
+    case Type::Number:
+      return rhs.as_number() == as_number();
+    case Type::String:
+      return rhs.as_string() == as_string();
+    case Type::Array: {
+      auto array = this->as_array();
+      if (rhs.array_size() != array.size()) {
+        return false;
+      }
+
+      bool rval = true;
+      size_t index = 0;
+      rhs.apply_array([&, this](adapter::Adapter const & elem) {
+        // Short-Circuit OK
+        rval = rval && array[index].equals(elem, strict);
+        ++index;
+        return Status::Accept;
+      });
+      return rval;
+    }
+    case Type::Object: {
+      auto object = this->as_object();
+      if (rhs.object_size() != object.size()) {
+        return false;
+      }
+
+      bool rval = true;
+      rhs.apply_object([&, this](std::string const & key, adapter::Adapter const & elem) {
+        // Short-Circuit OK
+        rval = rval && object.contains(key) && object[key].equals(elem, strict);
+        return Status::Accept;
+      });
+      return rval;
+    }
+    }
+  }
+
 protected:
   JSON * value() const { return value_; }
   JSON const & const_value() const { return value_ ? *value_ : AdapterTraits<JSON>::const_empty(); }

+ 29 - 10
include/jvalidate/validation_visitor.h

@@ -37,14 +37,12 @@ public:
   }
 
   Status visit(constraint::EnumConstraint const & cons) const {
-    // TODO(samjaffe) Implement
-    /* auto equals = [this](adapter::Adapter const & frozen) { return document_.equals(frozen); };
-     */
-    /* for (auto const & option : cons.enumeration) { */
-    /*   if (option->apply(equals)) { */
-    /*     return Status::Accept; */
-    /*   } */
-    /* } */
+    for (auto const & option : cons.enumeration) {
+      // TODO(samjaffe) Strictness
+      if (option->apply([this](auto const & frozen) { return document_.equals(frozen); })) {
+        return Status::Accept;
+      }
+    }
     return Status::Reject;
   }
 
@@ -204,7 +202,29 @@ public:
     return rval;
   }
 
-  Status visit(constraint::UniqueItemsConstraint const & cons) const { throw; }
+  Status visit(constraint::UniqueItemsConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+
+    if constexpr (std::totally_ordered<A>) {
+      std::set<A> cache;
+      for (A const & elem : document_.as_array()) {
+        if (not cache.insert(elem).second) {
+          return Status::Reject;
+        }
+      }
+    } else {
+      auto array = document_.as_array();
+      for (size_t i = 0; i < array.size(); ++i) {
+        for (size_t j = i + 1; j < array.size(); ++j) {
+          if (array[i].equals(array[j], true)) {
+            return Status::Reject;
+          }
+        }
+      }
+    }
+
+    return Status::Accept;
+  }
 
   Status visit(constraint::AdditionalPropertiesConstraint const & cons) const {
     NOOP_UNLESS_TYPE(Object);
@@ -258,7 +278,6 @@ public:
       }
 
       rval &= required.empty();
-
       if (!rval && result_ == nullptr) {
         break;
       }