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 {
70 let mut result = root.clone();
71 let path = path.strip_prefix("$.").unwrap_or(path);
72 let segments: Vec<&str> = path.split('.').collect();
73
74 let mut current = &mut result;
75 for (i, segment) in segments.iter().enumerate() {
76 if i == segments.len() - 1 {
77 if let Some(obj) = current.as_object_mut() {
79 obj.insert(segment.to_string(), value.clone());
80 }
81 } else {
82 if current.get(*segment).is_none() {
84 if let Some(obj) = current.as_object_mut() {
85 obj.insert(segment.to_string(), serde_json::json!({}));
86 }
87 }
88 match current.get_mut(*segment) {
89 Some(v) => current = v,
90 None => return result, }
92 }
93 }
94
95 result
96}
97
98enum PathSegment<'a> {
99 Field(&'a str),
100 Index(&'a str, usize),
101}
102
103fn split_path_segments(path: &str) -> Vec<PathSegment<'_>> {
104 let mut segments = Vec::new();
105 for part in path.split('.') {
106 match parse_index_segment(part) {
107 Some(segment) => segments.push(segment),
108 None => segments.push(PathSegment::Field(part)),
109 }
110 }
111 segments
112}
113
114fn parse_index_segment(part: &str) -> Option<PathSegment<'_>> {
120 let open = part.find('[')?;
121 if !part.ends_with(']') {
123 return None;
124 }
125 let close = part.len() - 1;
126 let inner_start = open + 1;
129 if inner_start > close {
130 return None;
131 }
132 let idx_str = part.get(inner_start..close)?;
136 let name = part.get(..open)?;
137 let idx = idx_str.parse::<usize>().ok()?;
138 Some(PathSegment::Index(name, idx))
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use serde_json::json;
145
146 #[test]
147 fn test_resolve_path_root() {
148 let input = json!({"a": 1});
149 assert_eq!(resolve_path(&input, "$"), input);
150 }
151
152 #[test]
153 fn test_resolve_path_simple_field() {
154 let input = json!({"name": "hello", "value": 42});
155 assert_eq!(resolve_path(&input, "$.name"), json!("hello"));
156 assert_eq!(resolve_path(&input, "$.value"), json!(42));
157 }
158
159 #[test]
160 fn test_resolve_path_nested() {
161 let input = json!({"a": {"b": {"c": 99}}});
162 assert_eq!(resolve_path(&input, "$.a.b.c"), json!(99));
163 }
164
165 #[test]
166 fn test_resolve_path_missing() {
167 let input = json!({"a": 1});
168 assert_eq!(resolve_path(&input, "$.missing"), Value::Null);
169 }
170
171 #[test]
172 fn test_resolve_path_array_index() {
173 let input = json!({"items": [10, 20, 30]});
174 assert_eq!(resolve_path(&input, "$.items[0]"), json!(10));
175 assert_eq!(resolve_path(&input, "$.items[2]"), json!(30));
176 }
177
178 #[test]
179 fn test_apply_input_path_default() {
180 let input = json!({"x": 1});
181 assert_eq!(apply_input_path(&input, None), input);
182 assert_eq!(apply_input_path(&input, Some("$")), input);
183 }
184
185 #[test]
186 fn test_apply_result_path_default() {
187 let input = json!({"x": 1});
188 let result = json!({"y": 2});
189 assert_eq!(apply_result_path(&input, &result, None), result);
191 assert_eq!(apply_result_path(&input, &result, Some("$")), result);
192 }
193
194 #[test]
195 fn test_apply_result_path_null() {
196 let input = json!({"x": 1});
197 let result = json!({"y": 2});
198 assert_eq!(apply_result_path(&input, &result, Some("null")), input);
200 }
201
202 #[test]
203 fn test_apply_result_path_nested() {
204 let input = json!({"x": 1});
205 let result = json!("hello");
206 let output = apply_result_path(&input, &result, Some("$.result"));
207 assert_eq!(output, json!({"x": 1, "result": "hello"}));
208 }
209
210 #[test]
211 fn test_set_at_path_non_object_intermediate() {
212 let input = json!({"x": 42});
215 let result = json!("hello");
216 let output = apply_result_path(&input, &result, Some("$.x.nested"));
217 assert_eq!(output, json!({"x": 42}));
219 }
220
221 #[test]
222 fn test_apply_output_path() {
223 let output = json!({"a": 1, "b": 2});
224 assert_eq!(apply_output_path(&output, Some("$.a")), json!(1));
225 assert_eq!(apply_output_path(&output, None), output);
226 }
227
228 #[test]
229 fn test_resolve_path_unclosed_bracket_does_not_panic() {
230 let input = json!({"arr": [1, 2, 3]});
233 assert_eq!(resolve_path(&input, "$.arr["), Value::Null);
236 }
237
238 #[test]
239 fn test_resolve_path_multibyte_after_bracket_does_not_panic() {
240 let input = json!({"x": [1, 2, 3]});
243 assert_eq!(resolve_path(&input, "$.x[é"), Value::Null);
244 assert_eq!(resolve_path(&input, "$.x[é]"), Value::Null);
246 }
247
248 #[test]
249 fn test_resolve_path_empty_brackets_do_not_panic() {
250 let input = json!({"x": [1, 2, 3]});
251 assert_eq!(resolve_path(&input, "$.x[]"), Value::Null);
252 }
253
254 #[test]
255 fn test_resolve_path_bracket_only_segment() {
256 let input = json!({"a": 1});
258 assert_eq!(resolve_path(&input, "$.["), Value::Null);
259 assert_eq!(resolve_path(&input, "$.]"), Value::Null);
260 }
261
262 #[test]
263 fn test_split_path_segments_well_formed_index_still_works() {
264 let input = json!({"items": [10, 20, 30]});
266 assert_eq!(resolve_path(&input, "$.items[1]"), json!(20));
267 let nested = json!({"a": {"b": [{"c": 7}]}});
268 assert_eq!(resolve_path(&nested, "$.a.b[0].c"), json!(7));
269 }
270
271 #[test]
272 fn test_apply_input_path_malformed_does_not_panic() {
273 let input = json!({"arr": [1, 2, 3]});
274 let _ = apply_input_path(&input, Some("$.arr["));
276 let _ = apply_output_path(&input, Some("$.x[é"));
277 }
278}