relative_pointer.h 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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 (size_t 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.ends_with('#')) {
  39. rval.requests_key_ = true;
  40. path.remove_suffix(1);
  41. }
  42. if (path.starts_with('0') && path != "0") {
  43. return unexpected("Cannot zero-prefix a relative pointer");
  44. }
  45. size_t read = 0;
  46. expected parent_steps = parse_integer<size_t>(path, {.read = read});
  47. // Will return unexpected if path contains any non-numeric characters, or
  48. // if path represents a number that is out of the bounds of a size_t.
  49. JVALIDATE_PROPIGATE_UNEXPECTED(parent_steps.transform_error(to_message));
  50. rval.parent_steps_ = *parent_steps;
  51. return rval;
  52. }
  53. /**
  54. * @brief Acquire either the key of the nth parent or a JSON value at some "cousin"
  55. * location in the overall document.
  56. *
  57. * @tparam A JSON Adapter type
  58. *
  59. * @param where The pointer representing the current location.
  60. * @param root The root document location (accessed by where == Pointer())
  61. *
  62. * @return The evaluation result as described by
  63. * https://json-schema.org/draft/2020-12/relative-json-pointer#rfc.section.4
  64. */
  65. template <Adapter A>
  66. std::variant<std::string, A> inspect(Pointer const & where, A const & root) const {
  67. if (requests_key_) {
  68. return where.parent(parent_steps_).back();
  69. }
  70. auto rval = where.parent(parent_steps_).walk(root);
  71. return pointer_.walk(rval);
  72. }
  73. friend std::ostream & operator<<(std::ostream & os, RelativePointer const & rel) {
  74. os << rel.parent_steps_;
  75. if (rel.requests_key_) {
  76. return os << '#';
  77. }
  78. os << rel.pointer_;
  79. return os;
  80. }
  81. private:
  82. size_t parent_steps_ = 0;
  83. bool requests_key_ = false;
  84. Pointer pointer_ = {};
  85. };
  86. }