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