#pragma once #include #include #include #include #include #include #include #include namespace jvalidate::adapter::detail { /** * @brief A basic implementation of an Adapter for object JSON, which acts like * a map. Implements the ObjectAdapter constraint. * * @tparam JSON The actual underlying json type * @tparam Adapter The concrete implementation class for the Adapter. Used to * proxy the mapped_type of the underlying JSON Object. */ template > class SimpleObjectAdapter { public: using underlying_iterator_t = decltype(std::declval().begin()); using const_iterator = JsonObjectIterator; SimpleObjectAdapter(JSON * value) : value_(value) {} size_t size() const { return value_ ? value_->size() : 0; } const_iterator find(std::string const & key) const { return std::find_if(begin(), end(), [&key](auto const & kv) { return kv.first == key; }); } bool contains(std::string const & key) const { return find(key) != end(); } Adapter operator[](std::string const & key) const { auto it = find(key); return it != end() ? it->second : Adapter(); } const_iterator begin() const { return value_ ? const_iterator(value_->begin()) : const_iterator(); } const_iterator end() const { return value_ ? const_iterator(value_->end()) : const_iterator(); } std::map operator*() const { using C = std::map; return value_ ? C(begin(), end()) : C(); } protected: JSON * value() const { return value_; } JSON const & const_value() const { return value_ ? *value_ : AdapterTraits::const_empty(); } private: JSON * value_; }; /** * @brief A basic implementation of an Adapter for array JSON, which acts like * a vector. Implements the ArrayAdapter constraint. * * @tparam JSON The actual underlying json type * @tparam Adapter The concrete implementation class for the Adapter. Used to * proxy the mapped_type of the underlying JSON Array. */ template > class SimpleArrayAdapter { public: using underlying_iterator_t = decltype(std::declval().begin()); using const_iterator = JsonArrayIterator; SimpleArrayAdapter(JSON * value) : value_(value) {} size_t size() const { return value_ ? value_->size() : 0; } Adapter operator[](size_t index) const { if (index > size()) { return Adapter(); } auto it = begin(); std::advance(it, index); return *it; } const_iterator begin() const { return value_ ? const_iterator(value_->begin()) : const_iterator(); } const_iterator end() const { return value_ ? const_iterator(value_->end()) : const_iterator(); } std::vector operator*() const { using C = std::vector; return value_ ? C(begin(), end()) : C(); } protected: JSON * value() const { return value_; } JSON const & const_value() const { return value_ ? *value_ : AdapterTraits::const_empty(); } private: JSON * value_; }; /** * @brief A basic implementation of an Adapter, for any JSON type that * implements standard components. * * Provides final implementations of the virtual methods of * jvalidate::detail::Adapter: * - apply_array(AdapterCallback) -> Status * - array_size() -> size_t * - apply_object(ObjectAdapterCallback) -> Status * - object_size() -> size_t * - equals(Adapter, bool) -> bool * - freeze() -> unique_ptr * * Fulfills the constraint jvalidate::Adapter by providing default * implementations of: * - as_array() -> SimpleArrayAdapter * - as_object() -> SimpleObjectAdapter * * Fulfills one third of the constraint jvalidate::MutableAdapter, iff CRTP * implements the other two constraints. * - assign(Const) -> void * * @tparam JSON The actual underlying json type * @tparam CRTP The concrete implementation class below this, using the * "Curiously Recursive Template Pattern". */ template > class SimpleAdapter : public Adapter { public: static constexpr bool is_mutable = not std::is_const_v && MutableObject().as_object())>; using value_type = std::remove_const_t; public: SimpleAdapter(JSON * value = nullptr) : value_(value) {} SimpleAdapter(JSON & value) : value_(&value) {} size_t array_size() const final { return self().as_array().size(); } CRTP operator[](size_t index) const { return self().as_array()[index]; } detail::SimpleArrayAdapter as_array() const { return value_; } Status apply_array(AdapterCallback const & cb) const final { Status result = Status::Noop; for (auto const & child : self().as_array()) { result = cb(child) & result; } return result; } size_t object_size() const final { return self().as_object().size(); } bool contains(std::string const & key) const { return self().as_object().contains(key); } CRTP operator[](std::string const & key) const { return self().as_object()[key]; } detail::SimpleObjectAdapter as_object() const { return value_; } Status apply_object(ObjectAdapterCallback const & cb) const final { Status result = Status::Noop; for (auto const & [key, child] : self().as_object()) { result = cb(key, child) & result; } return result; } std::unique_ptr freeze() const final { return std::make_unique>(const_value()); } void assign(Const const & frozen) const requires(is_mutable) { EXPECT_M(value_ != nullptr, "Failed to create a new element in parent object"); if (auto * this_frozen = dynamic_cast const *>(&frozen)) { *value_ = this_frozen->value(); return; } frozen.apply([this](Adapter const & adapter) { static_cast(this)->assign(adapter); return Status::Accept; }); } auto operator<=>(SimpleAdapter const & rhs) const requires std::totally_ordered { using ord = std::strong_ordering; // Optimization - first we compare pointers if (value_ == rhs.value_) { return ord::equal; } // TODO: Can I implement this as `return *value_ <=> *rhs.value_`? if (value_ && rhs.value_) { if (*value_ < *rhs.value_) { return ord::less; } if (*rhs.value_ < *value_) { return ord::greater; } return ord::equal; } // Treat JSON(null) and nullptr as equivalent 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(); switch (rhs_type) { case Type::Null: return maybe_null(strict); case Type::Boolean: if (std::optional maybe = maybe_boolean(strict)) { return rhs.as_boolean() == *maybe; } return false; case Type::Integer: if (std::optional maybe = maybe_integer(strict)) { return rhs.as_integer() == *maybe; } return false; case Type::Number: if (std::optional maybe = maybe_number(strict)) { return rhs.as_number() == *maybe; } return false; case Type::String: if (std::optional maybe = maybe_string(strict)) { return rhs.as_string() == *maybe; } return false; case Type::Array: { auto array_size = maybe_array_size(strict); if (not array_size) { return false; } if (rhs.array_size() != *array_size) { return false; } bool rval = true; auto array = this->as_array(); rhs.apply_array([&, this, index = 0UL](adapter::Adapter const & elem) mutable { // Short-Circuit OK rval = rval && array[index].equals(elem, strict); ++index; return Status::Accept; }); return rval; } case Type::Object: { auto object_size = maybe_object_size(strict); if (not object_size) { return false; } if (rhs.object_size() != *object_size) { return false; } bool rval = true; auto object = this->as_object(); 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; } } } public: JSON * value() const { return value_; } JSON const & const_value() const { return value_ ? *value_ : AdapterTraits::const_empty(); } private: CRTP const & self() const { return static_cast(*this); } private: JSON * value_; }; }