reference.h 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #pragma once
  2. #include <cstdlib>
  3. #include <ostream>
  4. #include <string>
  5. #include <string_view>
  6. #include <utility>
  7. #include <jvalidate/compat/hash.h>
  8. #include <jvalidate/detail/anchor.h>
  9. #include <jvalidate/detail/expect.h>
  10. #include <jvalidate/detail/out.h>
  11. #include <jvalidate/detail/pointer.h>
  12. #include <jvalidate/uri.h>
  13. namespace jvalidate::detail {
  14. class Reference;
  15. /**
  16. * @brief A class describing a "Reference without a JSON-Pointer" object.
  17. * For the sake of avoiding code-duplication, we implement References in terms
  18. * of this class, which makes this comment awkward.
  19. *
  20. * A RootReference refers to a URI and/or an Anchor object - and is specifically
  21. * meant for use with {@see ReferenceCache} and {@see ReferenceManager} to
  22. * create bindings beween the Anchor/URI points of $id/$anchor tags with their
  23. * absolute reference parents.
  24. *
  25. * Because of this, there is an intrinsic link between RootReference and
  26. * Reference objects, although it is not a 1-1 relationship.
  27. */
  28. class RootReference {
  29. private:
  30. friend class Reference;
  31. URI uri_;
  32. Anchor anchor_;
  33. public:
  34. RootReference() = default;
  35. // Piecewise constructor for RootReference, supports (URI) and (URI, Anchor)
  36. explicit RootReference(URI uri, Anchor anchor = {})
  37. : uri_(std::move(uri)), anchor_(std::move(anchor)) {}
  38. // Parser-ctor for RootReference, implemented in terms of
  39. // {@see RootReference::RootReference(std::string_view, out<size_t>)}, which
  40. // is also used by Reference's parser-ctor.
  41. explicit RootReference(std::string_view ref) : RootReference(ref, discard_out) {}
  42. bool is_relative() const { return uri_.is_relative(); }
  43. URI const & uri() const { return uri_; }
  44. Anchor const & anchor() const { return anchor_; }
  45. friend std::ostream & operator<<(std::ostream & os, RootReference const & self) {
  46. return os << self.uri_ << '#' << self.anchor_;
  47. }
  48. auto operator<=>(RootReference const &) const = default;
  49. private:
  50. /**
  51. * @brief Parse a RootReference out from a given textual representation.
  52. *
  53. * @param ref A string containing a URI and/or anchor. By convention - this
  54. * parameter should be "#" if it is refering to an empty RootReference.
  55. *
  56. * @param[out] end An output variable that tracks the end-position of the
  57. * anchor. When calling {@see Reference::Reference(std::string_view)}, this
  58. * lets us properly offset the view for the JSON-Pointer component without
  59. * needing to re-implement the code that scans for it.
  60. */
  61. RootReference(std::string_view ref, out<size_t> end) {
  62. // By default, RootReference will consume the entire input
  63. end = std::string::npos;
  64. // As also mentioned in URI, the fragment identifier is used in a
  65. // JSON-Reference to separate the URI from the Anchor/Pointer component(s)
  66. size_t const end_of_uri = ref.find('#');
  67. uri_ = URI(ref.substr(0, end_of_uri));
  68. // If there is not a fragment-separator, then this RootReference is all URI
  69. // There will be no Anchor or JSON-Pointer components to extract.
  70. if (end_of_uri == std::string::npos) {
  71. return;
  72. }
  73. // Skip past the "#"
  74. ref.remove_prefix(end_of_uri + 1);
  75. // Anchors prohibit most characters, so we can be sure that the first "/"
  76. // past the URI is the endpoint of the Anchor
  77. size_t const pointer_start = ref.find('/');
  78. anchor_ = Anchor(ref.substr(0, pointer_start));
  79. // Prohibit a trailing JSON-Pointer unless the caller provided the out-param
  80. EXPECT_M(end || pointer_start == std::string::npos, "JSON-Pointer is illegal in this context");
  81. // Ensure proper offset is applied: add pointer_start to end_of_uri because
  82. // we called remove_prefix on ref. Add an additional +1 because of the "#"
  83. if (pointer_start != std::string::npos) {
  84. end = pointer_start + end_of_uri + 1;
  85. }
  86. }
  87. };
  88. /**
  89. * @brief A Reference is a class describing a location of a JSON value. This may
  90. * describe an external document, and anchor within a document (jump to
  91. * location), and/or a path to a value from a parent location. References allow
  92. * us to combine all three of these properties together - although in practice
  93. * the Anchor field should be empty before it escapes the detail namespace.
  94. */
  95. class Reference {
  96. private:
  97. RootReference root_;
  98. Pointer pointer_;
  99. public:
  100. Reference() = default;
  101. // Piecewise constructor for References (URI) and (URI, Pointer)
  102. explicit Reference(URI const & uri, Pointer pointer = {})
  103. : root_(uri), pointer_(std::move(pointer)) {}
  104. // Piecewise constructor for References (URI, Anchor) and (URI, Anchor, Pointer)
  105. explicit Reference(URI const & uri, Anchor const & anchor, Pointer pointer = {})
  106. : root_(uri, anchor), pointer_(std::move(pointer)) {}
  107. // Piecewise constructor for References using RootReference
  108. explicit Reference(RootReference root, Pointer pointer = {})
  109. : root_(std::move(root)), pointer_(std::move(pointer)) {}
  110. explicit Reference(std::string_view ref) {
  111. size_t pointer_start = 0;
  112. root_ = RootReference(ref, pointer_start);
  113. if (pointer_start != std::string::npos) {
  114. pointer_ = ref.substr(pointer_start);
  115. }
  116. }
  117. URI const & uri() const { return root_.uri(); }
  118. Anchor const & anchor() const { return root_.anchor(); }
  119. Pointer const & pointer() const { return pointer_; }
  120. RootReference const & root() const { return root_; }
  121. Reference parent() const { return Reference(root_, pointer_.parent()); }
  122. /**
  123. * @brief Delegate function for {@see Pointer::operator/=}, but returning
  124. * this Reference.
  125. */
  126. Reference & operator/=(auto const & next) { return (pointer_ /= next, *this); }
  127. /**
  128. * @brief Delegate function for {@see Pointer::operator/}, but returning
  129. * a Reference.
  130. */
  131. Reference operator/(auto const & next) const { return Reference(*this) /= next; }
  132. friend std::ostream & operator<<(std::ostream & os, Reference const & self) {
  133. return os << self.root_ << self.pointer_;
  134. }
  135. auto operator<=>(Reference const &) const = default;
  136. };
  137. }
  138. template <> struct std::hash<jvalidate::detail::RootReference> {
  139. auto operator()(jvalidate::detail::RootReference const & value) const {
  140. std::size_t seed = 0;
  141. jvalidate::compat::hash_combine(seed, value.uri());
  142. jvalidate::compat::hash_combine(seed, value.anchor());
  143. return seed;
  144. }
  145. };
  146. template <> struct std::hash<jvalidate::detail::Reference> {
  147. auto operator()(jvalidate::detail::Reference const & value) const {
  148. std::size_t seed = 0;
  149. jvalidate::compat::hash_combine(seed, value.root());
  150. jvalidate::compat::hash_combine(seed, value.pointer());
  151. return seed;
  152. }
  153. };