1use serde_json::Value;
4
5pub fn resolve_pointer<'a>(value: &'a Value, pointer: &str) -> Option<&'a Value> {
28 if pointer.is_empty() {
30 return Some(value);
31 }
32
33 if !pointer.starts_with('/') {
35 return None;
36 }
37
38 let mut current = value;
39
40 for token in pointer[1..].split('/') {
41 let unescaped = unescape_token(token);
43
44 current = match current {
45 Value::Object(map) => map.get(&unescaped)?,
46 Value::Array(arr) => {
47 let idx: usize = unescaped.parse().ok()?;
49 arr.get(idx)?
50 }
51 _ => return None,
52 };
53 }
54
55 Some(current)
56}
57
58fn unescape_token(token: &str) -> String {
62 token.replace("~1", "/").replace("~0", "~")
63}
64
65#[allow(dead_code)]
69pub fn escape_token(s: &str) -> String {
70 s.replace('~', "~0").replace('/', "~1")
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use serde_json::json;
77
78 #[test]
79 fn test_empty_pointer() {
80 let doc = json!({"foo": 1});
81 assert_eq!(resolve_pointer(&doc, ""), Some(&doc));
82 }
83
84 #[test]
85 fn test_simple_path() {
86 let doc = json!({"foo": {"bar": 42}});
87 assert_eq!(resolve_pointer(&doc, "/foo"), Some(&json!({"bar": 42})));
88 assert_eq!(resolve_pointer(&doc, "/foo/bar"), Some(&json!(42)));
89 }
90
91 #[test]
92 fn test_array_index() {
93 let doc = json!({"items": [10, 20, 30]});
94 assert_eq!(resolve_pointer(&doc, "/items/0"), Some(&json!(10)));
95 assert_eq!(resolve_pointer(&doc, "/items/2"), Some(&json!(30)));
96 assert_eq!(resolve_pointer(&doc, "/items/3"), None);
97 }
98
99 #[test]
100 fn test_escaped_tokens() {
101 let doc = json!({"a/b": {"c~d": 1}});
102 assert_eq!(resolve_pointer(&doc, "/a~1b/c~0d"), Some(&json!(1)));
103 }
104
105 #[test]
106 fn test_invalid_pointer() {
107 let doc = json!({"foo": 1});
108 assert_eq!(resolve_pointer(&doc, "foo"), None);
110 assert_eq!(resolve_pointer(&doc, "/bar"), None);
112 }
113
114 #[test]
115 fn test_nested_arrays() {
116 let doc = json!({"matrix": [[1, 2], [3, 4]]});
117 assert_eq!(resolve_pointer(&doc, "/matrix/0/1"), Some(&json!(2)));
118 assert_eq!(resolve_pointer(&doc, "/matrix/1/0"), Some(&json!(3)));
119 }
120
121 #[test]
122 fn test_escape_token() {
123 assert_eq!(escape_token("a/b"), "a~1b");
124 assert_eq!(escape_token("a~b"), "a~0b");
125 assert_eq!(escape_token("a/b~c"), "a~1b~0c");
126 }
127}