#pragma once #include #include #include #include #include #include #include namespace jvalidate::detail { class Reference; /** * @brief A class describing a "Reference without a JSON-Pointer" object. * For the sake of avoiding code-duplication, we implement References in terms * of this class, which makes this comment awkward. * * A RootReference refers to a URI and/or an Anchor object - and is specifically * meant for use with {@see ReferenceCache} and {@see ReferenceManager} to * create bindings beween the Anchor/URI points of $id/$anchor tags with their * absolute reference parents. * * Because of this, there is an intrinsic link between RootReference and * Reference objects, although it is not a 1-1 relationship. */ class RootReference { private: friend class Reference; URI uri_; Anchor anchor_; public: RootReference() = default; // Piecewise constructor for RootReference, supports (URI) and (URI, Anchor) explicit RootReference(URI const & uri, Anchor const & anchor = {}) : uri_(uri), anchor_(anchor) {} // Parser-ctor for RootReference, implemented in terms of // {@see RootReference::RootReference(std::string_view, out)}, which // is also used by Reference's parser-ctor. explicit RootReference(std::string_view ref) : RootReference(ref, discard_out) {} bool is_relative() const { return uri_.is_relative(); } URI const & uri() const { return uri_; } Anchor const & anchor() const { return anchor_; } friend std::ostream & operator<<(std::ostream & os, RootReference const & self) { return os << self.uri_ << '#' << self.anchor_; } auto operator<=>(RootReference const &) const = default; private: /** * @brief Parse a RootReference out from a given textual representation. * * @param ref A string containing a URI and/or anchor. By convention - this * parameter should be "#" if it is refering to an empty RootReference. * * @param[out] end An output variable that tracks the end-position of the * anchor. When calling {@see Reference::Reference(std::string_view)}, this * lets us properly offset the view for the JSON-Pointer component without * needing to re-implement the code that scans for it. */ RootReference(std::string_view ref, out end) { // By default, RootReference will consume the entire input end = std::string::npos; // As also mentioned in URI, the fragment identifier is used in a // JSON-Reference to separate the URI from the Anchor/Pointer component(s) size_t end_of_uri = ref.find('#'); uri_ = URI(ref.substr(0, end_of_uri)); // If there is not a fragment-separator, then this RootReference is all URI // There will be no Anchor or JSON-Pointer components to extract. if (end_of_uri == std::string::npos) { return; } // Skip past the "#" ref.remove_prefix(end_of_uri + 1); // Anchors prohibit most characters, so we can be sure that the first "/" // past the URI is the endpoint of the Anchor size_t const pointer_start = ref.find('/'); anchor_ = Anchor(ref.substr(0, pointer_start)); // Prohibit a trailing JSON-Pointer unless the caller provided the out-param EXPECT_M(end || pointer_start == std::string::npos, "JSON-Pointer is illegal in this context"); // Ensure proper offset is applied: add pointer_start to end_of_uri because // we called remove_prefix on ref. Add an additional +1 because of the "#" if (pointer_start != std::string::npos) { end = pointer_start + end_of_uri + 1; } } }; /** * @brief A Reference is a class describing a location of a JSON value. This may * describe an external document, and anchor within a document (jump to * location), and/or a path to a value from a parent location. References allow * us to combine all three of these properties together - although in practice * the Anchor field should be empty before it escapes the detail namespace. */ class Reference { private: RootReference root_; Pointer pointer_; public: Reference() = default; // Piecewise constructor for References (URI) and (URI, Pointer) explicit Reference(URI const & uri, Pointer const & pointer = {}) : root_(uri), pointer_(pointer) {} // Piecewise constructor for References (URI, Anchor) and (URI, Anchor, Pointer) explicit Reference(URI const & uri, Anchor const & anchor, Pointer const & pointer = {}) : root_(uri, anchor), pointer_(pointer) {} // Piecewise constructor for References using RootReference explicit Reference(RootReference const & root, Pointer const & pointer = {}) : root_(root), pointer_(pointer) {} explicit Reference(std::string_view ref) { size_t pointer_start = 0; root_ = RootReference(ref, pointer_start); if (pointer_start != std::string::npos) { pointer_ = ref.substr(pointer_start); } } URI const & uri() const { return root_.uri(); } Anchor const & anchor() const { return root_.anchor(); } Pointer const & pointer() const { return pointer_; } RootReference const & root() const { return root_; } Reference parent() const { return Reference(root_, pointer_.parent()); } /** * @brief Delegate function for {@see Pointer::operator/=}, but returning * this Reference. */ Reference & operator/=(auto const & in) { return (pointer_ /= in, *this); } /** * @brief Delegate function for {@see Pointer::operator/}, but returning * a Reference. */ Reference operator/(auto const & in) const { return Reference(*this) /= in; } friend std::ostream & operator<<(std::ostream & os, Reference const & self) { return os << self.root_ << self.pointer_; } auto operator<=>(Reference const &) const = default; }; }