relative_pointer.h 3.0 KB

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