Skip to main content

etdl_parser/
jsonptr.rs

1pub fn resolve_json_pointer<'a>(
2    root: &'a serde_json::Value,
3    pointer: &str,
4) -> Option<&'a serde_json::Value> {
5    if pointer.is_empty() || pointer == "#" {
6        return Some(root);
7    }
8
9    let path = if pointer.starts_with("#/") {
10        &pointer[2..]
11    } else if pointer.starts_with('/') {
12        &pointer[1..]
13    } else {
14        return None;
15    };
16
17    if path.is_empty() {
18        return Some(root);
19    }
20
21    let segments = parse_pointer_segments(path);
22    let mut current = root;
23
24    for segment in segments {
25        match current {
26            serde_json::Value::Object(map) => {
27                current = map.get(&segment)?;
28            }
29            serde_json::Value::Array(arr) => {
30                let index: usize = segment.parse().ok()?;
31                current = arr.get(index)?;
32            }
33            _ => return None,
34        }
35    }
36
37    Some(current)
38}
39
40fn parse_pointer_segments(path: &str) -> Vec<String> {
41    let mut segments = Vec::new();
42    let mut current = String::new();
43    let mut chars = path.chars();
44
45    while let Some(c) = chars.next() {
46        if c == '/' {
47            segments.push(std::mem::take(&mut current));
48        } else if c == '~' {
49            match chars.next() {
50                Some('0') => current.push('~'),
51                Some('1') => current.push('/'),
52                _ => {} 
53            }
54        } else {
55            current.push(c);
56        }
57    }
58    segments.push(current);
59
60    segments
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use serde_json::json;
67
68    #[test]
69    fn test_resolve_root() {
70        let doc = json!({"a": 1});
71        assert_eq!(resolve_json_pointer(&doc, ""), Some(&doc));
72        assert_eq!(resolve_json_pointer(&doc, "#"), Some(&doc));
73        assert_eq!(resolve_json_pointer(&doc, "#/"), Some(&doc));
74    }
75
76    #[test]
77    fn test_resolve_nested() {
78        let doc = json!({
79            "components": {
80                "messages": {
81                    "OrderPlaced": {
82                        "payload": {"type": "object"}
83                    }
84                }
85            }
86        });
87
88        let resolved = resolve_json_pointer(&doc, "#/components/messages/OrderPlaced");
89        assert!(resolved.is_some());
90        assert_eq!(
91            resolved.unwrap(),
92            &json!({"payload": {"type": "object"}})
93        );
94    }
95
96    #[test]
97    fn test_resolve_array_index() {
98        let doc = json!({
99            "items": [{"name": "a"}, {"name": "b"}]
100        });
101        let resolved = resolve_json_pointer(&doc, "#/items/1/name");
102        assert_eq!(resolved, Some(&json!("b")));
103    }
104
105    #[test]
106    fn test_escape_sequences() {
107        let doc = json!({
108            "a~b/c": "value"
109        });
110        let resolved = resolve_json_pointer(&doc, "#/a~0b~1c");
111        assert_eq!(resolved, Some(&json!("value")));
112    }
113}