relative_pointer.h 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #pragma once
  2. #include <cstdlib>
  3. #include <ostream>
  4. #include <string>
  5. #include <string_view>
  6. #include <variant>
  7. #include <jvalidate/detail/expect.h>
  8. #include <jvalidate/detail/number.h>
  9. #include <jvalidate/detail/pointer.h>
  10. #include <jvalidate/forward.h>
  11. namespace jvalidate::detail {
  12. /**
  13. * @brief A special form of json pointer that is allowed to specify moving up
  14. * into the parent scope of the current location.
  15. * A relative pointer has two forms:
  16. * - Nth parent-key, which takes the ABNF form:
  17. * non-negative-integer "#"
  18. * - Nth parent followed by a JSON-Pointer as specified by RFC6901
  19. *
  20. * This does not comply with array neighbor offsets as described in
  21. * Section 4 Paragraph 3 (the "index-manipulation" ABNF).
  22. *
  23. * https://json-schema.org/draft/2020-12/relative-json-pointer
  24. * https://datatracker.ietf.org/doc/html/rfc6901
  25. */
  26. class RelativePointer {
  27. public:
  28. explicit(false) RelativePointer(std::string_view path) {
  29. if (path == "0") { // A literal RelativePointer of "0" simply means "here"
  30. return;
  31. }
  32. if (size_t const pos = path.find('/'); pos != std::string_view::npos) {
  33. // Handle the JSON-Pointer version
  34. pointer_ = Pointer(path.substr(pos));
  35. path.remove_suffix(path.size() - pos);
  36. } else if (path.ends_with('#')) {
  37. requests_key_ = true;
  38. path.remove_suffix(1);
  39. }
  40. EXPECT_M(path == "0" || not path.starts_with("0"), "Cannot zero-prefix a relative pointer");
  41. // Will throw an exception if path contains any non-numeric characters, or
  42. // if path represents a number that is out of the bounds of a size_t.
  43. parent_steps_ = from_str<size_t>(path);
  44. }
  45. /**
  46. * @brief Acquire either the key of the nth parent or a JSON value at some "cousin"
  47. * location in the overall document.
  48. *
  49. * @tparam A JSON Adapter type
  50. *
  51. * @param where The pointer representing the current location.
  52. * @param root The root document location (accessed by where == Pointer())
  53. *
  54. * @return The evaluation result as described by
  55. * https://json-schema.org/draft/2020-12/relative-json-pointer#rfc.section.4
  56. */
  57. template <Adapter A>
  58. std::variant<std::string, A> inspect(Pointer const & where, A const & root) const {
  59. if (requests_key_) {
  60. return where.parent(parent_steps_).back();
  61. }
  62. auto rval = where.parent(parent_steps_).walk(root);
  63. return pointer_.walk(rval);
  64. }
  65. friend std::ostream & operator<<(std::ostream & os, RelativePointer const & rel) {
  66. os << rel.parent_steps_;
  67. if (rel.requests_key_) {
  68. return os << '#';
  69. }
  70. os << rel.pointer_;
  71. return os;
  72. }
  73. private:
  74. size_t parent_steps_ = 0;
  75. bool requests_key_ = false;
  76. Pointer pointer_;
  77. };
  78. }