relative_pointer.h 3.1 KB

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