1use serde_json::Value;
8
9pub(crate) fn resolve_path<'a>(data: &'a Value, path: &str) -> Option<&'a Value> {
16 if path.is_empty() || path == "/" {
17 return Some(data);
18 }
19
20 let trimmed = path.strip_prefix('/').unwrap_or(path);
21 let segments: Vec<&str> = trimmed.split('/').collect();
22
23 let mut current = data;
24 for segment in segments {
25 if segment.is_empty() {
26 continue;
27 }
28 match current {
29 Value::Object(map) => {
30 current = map.get(segment)?;
31 }
32 Value::Array(arr) => {
33 let index: usize = segment.parse().ok()?;
34 current = arr.get(index)?;
35 }
36 _ => return None,
37 }
38 }
39
40 Some(current)
41}
42
43pub(crate) fn resolve_path_string(data: &Value, path: &str) -> Option<String> {
49 let value = resolve_path(data, path)?;
50 match value {
51 Value::String(s) => Some(s.clone()),
52 Value::Number(n) => Some(n.to_string()),
53 Value::Bool(b) => Some(b.to_string()),
54 Value::Null => None,
55 Value::Array(_) | Value::Object(_) => serde_json::to_string(value).ok(),
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use serde_json::json;
63
64 #[test]
65 fn simple_key_resolution() {
66 let data = json!({"name": "Alice"});
67 assert_eq!(resolve_path(&data, "/name"), Some(&json!("Alice")));
68 }
69
70 #[test]
71 fn nested_key_resolution() {
72 let data = json!({"user": {"name": "Bob"}});
73 assert_eq!(resolve_path(&data, "/user/name"), Some(&json!("Bob")));
74 }
75
76 #[test]
77 fn array_index_resolution() {
78 let data = json!({"users": [{"name": "Carol"}]});
79 assert_eq!(resolve_path(&data, "/users/0/name"), Some(&json!("Carol")));
80 }
81
82 #[test]
83 fn missing_key_returns_none() {
84 let data = json!({"name": "Alice"});
85 assert_eq!(resolve_path(&data, "/missing"), None);
86 }
87
88 #[test]
89 fn empty_path_returns_root() {
90 let data = json!({"name": "Alice"});
91 assert_eq!(resolve_path(&data, ""), Some(&data));
92 }
93
94 #[test]
95 fn root_slash_returns_root() {
96 let data = json!({"name": "Alice"});
97 assert_eq!(resolve_path(&data, "/"), Some(&data));
98 }
99
100 #[test]
101 fn numeric_value_resolution() {
102 let data = json!({"count": 42});
103 let result = resolve_path(&data, "/count");
104 assert_eq!(result, Some(&json!(42)));
105 assert!(result.unwrap().is_number());
106 }
107
108 #[test]
109 fn boolean_resolution() {
110 let data = json!({"active": true});
111 let result = resolve_path(&data, "/active");
112 assert_eq!(result, Some(&json!(true)));
113 assert!(result.unwrap().is_boolean());
114 }
115
116 #[test]
117 fn null_value_resolve_path() {
118 let data = json!({"deleted_at": null});
119 let result = resolve_path(&data, "/deleted_at");
120 assert_eq!(result, Some(&Value::Null));
121 }
122
123 #[test]
124 fn null_value_resolve_path_string_returns_none() {
125 let data = json!({"deleted_at": null});
126 assert_eq!(resolve_path_string(&data, "/deleted_at"), None);
127 }
128
129 #[test]
130 fn deep_nesting() {
131 let data = json!({"a": {"b": {"c": {"d": "deep"}}}});
132 assert_eq!(resolve_path(&data, "/a/b/c/d"), Some(&json!("deep")));
133 }
134
135 #[test]
136 fn invalid_array_index_returns_none() {
137 let data = json!({"items": [1, 2, 3]});
138 assert_eq!(resolve_path(&data, "/items/5"), None);
139 assert_eq!(resolve_path(&data, "/items/abc"), None);
140 }
141
142 #[test]
143 fn resolve_path_string_for_string() {
144 let data = json!({"name": "Alice"});
145 assert_eq!(
146 resolve_path_string(&data, "/name"),
147 Some("Alice".to_string())
148 );
149 }
150
151 #[test]
152 fn resolve_path_string_for_number() {
153 let data = json!({"count": 42});
154 assert_eq!(resolve_path_string(&data, "/count"), Some("42".to_string()));
155 }
156
157 #[test]
158 fn resolve_path_string_for_boolean() {
159 let data = json!({"active": true});
160 assert_eq!(
161 resolve_path_string(&data, "/active"),
162 Some("true".to_string())
163 );
164 }
165
166 #[test]
167 fn resolve_path_string_for_object() {
168 let data = json!({"user": {"name": "Alice"}});
169 let result = resolve_path_string(&data, "/user");
170 assert_eq!(result, Some(r#"{"name":"Alice"}"#.to_string()));
171 }
172
173 #[test]
174 fn resolve_path_string_for_array() {
175 let data = json!({"items": [1, 2, 3]});
176 let result = resolve_path_string(&data, "/items");
177 assert_eq!(result, Some("[1,2,3]".to_string()));
178 }
179
180 #[test]
181 fn resolve_path_string_missing_returns_none() {
182 let data = json!({"name": "Alice"});
183 assert_eq!(resolve_path_string(&data, "/missing"), None);
184 }
185
186 #[test]
187 fn resolve_path_on_non_object_non_array() {
188 let data = json!("just a string");
189 assert_eq!(resolve_path(&data, "/anything"), None);
190 }
191
192 #[test]
193 fn nested_array_access() {
194 let data = json!({"matrix": [[1, 2], [3, 4]]});
195 assert_eq!(resolve_path(&data, "/matrix/1/0"), Some(&json!(3)));
196 }
197}