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