1use serde_json::Value;
2
3pub fn apply_input_path(input: &Value, path: Option<&str>) -> Value {
8 match path {
9 None | Some("$") => input.clone(),
10 Some(p) => resolve_path(input, p),
11 }
12}
13
14pub fn apply_output_path(output: &Value, path: Option<&str>) -> Value {
17 match path {
18 None | Some("$") => output.clone(),
19 Some(p) => resolve_path(output, p),
20 }
21}
22
23pub fn apply_result_path(input: &Value, result: &Value, path: Option<&str>) -> Value {
28 match path {
29 None | Some("$") => result.clone(),
30 Some("null") => input.clone(),
31 Some(p) => set_at_path(input, p, result),
32 }
33}
34
35pub fn resolve_path(root: &Value, path: &str) -> Value {
38 if path == "$" {
39 return root.clone();
40 }
41
42 let path = path.strip_prefix("$.").unwrap_or(path);
43 let mut current = root;
44
45 for segment in split_path_segments(path) {
46 match segment {
47 PathSegment::Field(name) => {
48 current = match current.get(name) {
49 Some(v) => v,
50 None => return Value::Null,
51 };
52 }
53 PathSegment::Index(name, idx) => {
54 current = match current.get(name) {
55 Some(v) => match v.get(idx) {
56 Some(v) => v,
57 None => return Value::Null,
58 },
59 None => return Value::Null,
60 };
61 }
62 }
63 }
64
65 current.clone()
66}
67
68fn set_at_path(root: &Value, path: &str, value: &Value) -> Value {
73 let mut result = root.clone();
74 let path = path.strip_prefix("$.").unwrap_or(path);
75 let segments = split_path_segments(path);
76
77 fn assign(current: &mut Value, segments: &[PathSegment<'_>], value: &Value) {
78 let (segment, rest) = match segments.split_first() {
79 Some(pair) => pair,
80 None => return,
81 };
82 let is_last = rest.is_empty();
83 match segment {
84 PathSegment::Field(name) => {
85 if let Some(obj) = current.as_object_mut() {
86 if is_last {
87 obj.insert(name.to_string(), value.clone());
88 } else {
89 let child = obj
90 .entry(name.to_string())
91 .or_insert_with(|| serde_json::json!({}));
92 assign(child, rest, value);
93 }
94 }
95 }
96 PathSegment::Index(name, idx) => {
97 if let Some(obj) = current.as_object_mut() {
99 let arr_val = obj
100 .entry(name.to_string())
101 .or_insert_with(|| Value::Array(Vec::new()));
102 if !arr_val.is_array() {
103 *arr_val = Value::Array(Vec::new());
104 }
105 if let Some(arr) = arr_val.as_array_mut() {
106 if arr.len() <= *idx {
107 arr.resize(idx + 1, Value::Null);
108 }
109 if is_last {
110 arr[*idx] = value.clone();
111 } else {
112 if !arr[*idx].is_object() && !arr[*idx].is_array() {
113 arr[*idx] = serde_json::json!({});
114 }
115 assign(&mut arr[*idx], rest, value);
116 }
117 }
118 }
119 }
120 }
121 }
122
123 assign(&mut result, &segments, value);
124 result
125}
126
127enum PathSegment<'a> {
128 Field(&'a str),
129 Index(&'a str, usize),
130}
131
132fn split_path_segments(path: &str) -> Vec<PathSegment<'_>> {
133 let mut segments = Vec::new();
134 for part in path.split('.') {
135 match parse_index_segment(part) {
136 Some(segment) => segments.push(segment),
137 None => segments.push(PathSegment::Field(part)),
138 }
139 }
140 segments
141}
142
143fn parse_index_segment(part: &str) -> Option<PathSegment<'_>> {
149 let open = part.find('[')?;
150 if !part.ends_with(']') {
152 return None;
153 }
154 let close = part.len() - 1;
155 let inner_start = open + 1;
158 if inner_start > close {
159 return None;
160 }
161 let idx_str = part.get(inner_start..close)?;
165 let name = part.get(..open)?;
166 let idx = idx_str.parse::<usize>().ok()?;
167 Some(PathSegment::Index(name, idx))
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use serde_json::json;
174
175 #[test]
176 fn test_resolve_path_root() {
177 let input = json!({"a": 1});
178 assert_eq!(resolve_path(&input, "$"), input);
179 }
180
181 #[test]
182 fn test_resolve_path_simple_field() {
183 let input = json!({"name": "hello", "value": 42});
184 assert_eq!(resolve_path(&input, "$.name"), json!("hello"));
185 assert_eq!(resolve_path(&input, "$.value"), json!(42));
186 }
187
188 #[test]
189 fn test_resolve_path_nested() {
190 let input = json!({"a": {"b": {"c": 99}}});
191 assert_eq!(resolve_path(&input, "$.a.b.c"), json!(99));
192 }
193
194 #[test]
195 fn test_resolve_path_missing() {
196 let input = json!({"a": 1});
197 assert_eq!(resolve_path(&input, "$.missing"), Value::Null);
198 }
199
200 #[test]
201 fn test_resolve_path_array_index() {
202 let input = json!({"items": [10, 20, 30]});
203 assert_eq!(resolve_path(&input, "$.items[0]"), json!(10));
204 assert_eq!(resolve_path(&input, "$.items[2]"), json!(30));
205 }
206
207 #[test]
208 fn test_apply_input_path_default() {
209 let input = json!({"x": 1});
210 assert_eq!(apply_input_path(&input, None), input);
211 assert_eq!(apply_input_path(&input, Some("$")), input);
212 }
213
214 #[test]
215 fn test_apply_result_path_default() {
216 let input = json!({"x": 1});
217 let result = json!({"y": 2});
218 assert_eq!(apply_result_path(&input, &result, None), result);
220 assert_eq!(apply_result_path(&input, &result, Some("$")), result);
221 }
222
223 #[test]
224 fn test_apply_result_path_null() {
225 let input = json!({"x": 1});
226 let result = json!({"y": 2});
227 assert_eq!(apply_result_path(&input, &result, Some("null")), input);
229 }
230
231 #[test]
232 fn test_apply_result_path_nested() {
233 let input = json!({"x": 1});
234 let result = json!("hello");
235 let output = apply_result_path(&input, &result, Some("$.result"));
236 assert_eq!(output, json!({"x": 1, "result": "hello"}));
237 }
238
239 #[test]
240 fn test_set_at_path_non_object_intermediate() {
241 let input = json!({"x": 42});
244 let result = json!("hello");
245 let output = apply_result_path(&input, &result, Some("$.x.nested"));
246 assert_eq!(output, json!({"x": 42}));
248 }
249
250 #[test]
253 fn test_set_at_path_array_index_leaf() {
254 let input = json!({"a": [1, 2, 3]});
255 let result = json!(99);
256 let output = apply_result_path(&input, &result, Some("$.a[1]"));
257 assert_eq!(output, json!({"a": [1, 99, 3]}));
258 assert!(output.get("a[1]").is_none());
260 }
261
262 #[test]
263 fn test_set_at_path_array_index_then_field() {
264 let input = json!({"items": [{"v": 1}]});
265 let result = json!("done");
266 let output = apply_result_path(&input, &result, Some("$.items[0].status"));
267 assert_eq!(output, json!({"items": [{"v": 1, "status": "done"}]}));
268 }
269
270 #[test]
271 fn test_set_at_path_array_index_grows_array() {
272 let input = json!({});
273 let result = json!("x");
274 let output = apply_result_path(&input, &result, Some("$.a[2]"));
275 assert_eq!(output, json!({"a": [null, null, "x"]}));
276 }
277
278 #[test]
279 fn test_apply_output_path() {
280 let output = json!({"a": 1, "b": 2});
281 assert_eq!(apply_output_path(&output, Some("$.a")), json!(1));
282 assert_eq!(apply_output_path(&output, None), output);
283 }
284
285 #[test]
286 fn test_resolve_path_unclosed_bracket_does_not_panic() {
287 let input = json!({"arr": [1, 2, 3]});
290 assert_eq!(resolve_path(&input, "$.arr["), Value::Null);
293 }
294
295 #[test]
296 fn test_resolve_path_multibyte_after_bracket_does_not_panic() {
297 let input = json!({"x": [1, 2, 3]});
300 assert_eq!(resolve_path(&input, "$.x[é"), Value::Null);
301 assert_eq!(resolve_path(&input, "$.x[é]"), Value::Null);
303 }
304
305 #[test]
306 fn test_resolve_path_empty_brackets_do_not_panic() {
307 let input = json!({"x": [1, 2, 3]});
308 assert_eq!(resolve_path(&input, "$.x[]"), Value::Null);
309 }
310
311 #[test]
312 fn test_resolve_path_bracket_only_segment() {
313 let input = json!({"a": 1});
315 assert_eq!(resolve_path(&input, "$.["), Value::Null);
316 assert_eq!(resolve_path(&input, "$.]"), Value::Null);
317 }
318
319 #[test]
320 fn test_split_path_segments_well_formed_index_still_works() {
321 let input = json!({"items": [10, 20, 30]});
323 assert_eq!(resolve_path(&input, "$.items[1]"), json!(20));
324 let nested = json!({"a": {"b": [{"c": 7}]}});
325 assert_eq!(resolve_path(&nested, "$.a.b[0].c"), json!(7));
326 }
327
328 #[test]
329 fn test_apply_input_path_malformed_does_not_panic() {
330 let input = json!({"arr": [1, 2, 3]});
331 let _ = apply_input_path(&input, Some("$.arr["));
333 let _ = apply_output_path(&input, Some("$.x[é"));
334 }
335}