1use serde_json::Value;
2
3use crate::jsonpath::{JsonPath, PathError, Step};
4
5pub type StatesError = (String, String);
7
8pub fn apply_input_path(input: &Value, path: Option<&str>) -> Result<Value, StatesError> {
14 match path {
15 None | Some("$") => Ok(input.clone()),
16 Some(p) => resolve_reference(input, p),
17 }
18}
19
20pub fn apply_output_path(output: &Value, path: Option<&str>) -> Result<Value, StatesError> {
23 match path {
24 None | Some("$") => Ok(output.clone()),
25 Some(p) => resolve_reference(output, p),
26 }
27}
28
29pub fn apply_result_path(input: &Value, result: &Value, path: Option<&str>) -> Value {
34 match path {
35 None | Some("$") => result.clone(),
36 Some("null") => input.clone(),
37 Some(p) => set_at_path(input, p, result),
38 }
39}
40
41pub fn resolve_path(root: &Value, path: &str) -> Result<Value, PathError> {
43 crate::jsonpath::evaluate(root, path)
44}
45
46pub fn resolve_reference(root: &Value, path: &str) -> Result<Value, StatesError> {
50 resolve_path(root, path).map_err(|e| runtime_error(path_error_cause(path, &e)))
51}
52
53pub fn resolve_reference_with_context(
56 input: &Value,
57 context: Option<&Value>,
58 path: &str,
59) -> Result<Value, StatesError> {
60 resolve_with_context(input, context, path)
61 .map_err(|e| runtime_error(path_error_cause(path, &e)))
62}
63
64pub fn resolve_with_context(
67 input: &Value,
68 context: Option<&Value>,
69 path: &str,
70) -> Result<Value, PathError> {
71 match path.strip_prefix("$$") {
72 Some(rest) => match context {
73 Some(ctx) => resolve_path(ctx, &format!("${rest}")),
74 None => Ok(Value::Null),
75 },
76 None => resolve_path(input, path),
77 }
78}
79
80pub fn path_error_cause(path: &str, err: &PathError) -> String {
81 match err {
82 PathError::NoResults(norm) => {
83 format!("Invalid path '{path}' : No results for path: {norm}")
84 }
85 PathError::Invalid(msg) => format!("Invalid path '{path}' : {msg}"),
86 }
87}
88
89pub fn runtime_error(cause: String) -> StatesError {
90 ("States.Runtime".to_string(), cause)
91}
92
93fn set_at_path(root: &Value, path: &str, value: &Value) -> Value {
98 let mut result = root.clone();
99 let Some(steps) = JsonPath::parse(path).ok().and_then(|p| p.reference_steps()) else {
100 return result;
101 };
102
103 fn container_for(next: &Step) -> Value {
105 match next {
106 Step::Name(_) => serde_json::json!({}),
107 Step::Index(_) => Value::Array(Vec::new()),
108 }
109 }
110
111 fn assign(current: &mut Value, steps: &[Step], value: &Value) {
112 let Some((step, rest)) = steps.split_first() else {
113 *current = value.clone();
114 return;
115 };
116 match step {
117 Step::Name(name) => {
118 if let Some(obj) = current.as_object_mut() {
119 match rest.first() {
120 None => {
121 obj.insert(name.clone(), value.clone());
122 }
123 Some(next) => {
124 let child = obj
125 .entry(name.clone())
126 .or_insert_with(|| container_for(next));
127 assign(child, rest, value);
128 }
129 }
130 }
131 }
132 Step::Index(idx) => {
133 if let Some(arr) = current.as_array_mut() {
134 let idx = if *idx < 0 {
135 match usize::try_from(arr.len() as i64 + idx) {
136 Ok(i) => i,
137 Err(_) => return,
138 }
139 } else {
140 *idx as usize
141 };
142 if arr.len() <= idx {
143 arr.resize(idx + 1, Value::Null);
144 }
145 if let Some(next) = rest.first() {
146 if arr[idx].is_null() {
147 arr[idx] = container_for(next);
148 }
149 }
150 assign(&mut arr[idx], rest, value);
151 }
152 }
153 }
154 }
155
156 assign(&mut result, &steps, value);
157 result
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use serde_json::json;
164
165 #[test]
166 fn test_resolve_path_root() {
167 let input = json!({"a": 1});
168 assert_eq!(resolve_path(&input, "$").unwrap(), input);
169 }
170
171 #[test]
172 fn test_resolve_path_simple_field() {
173 let input = json!({"name": "hello", "value": 42});
174 assert_eq!(resolve_path(&input, "$.name").unwrap(), json!("hello"));
175 assert_eq!(resolve_path(&input, "$.value").unwrap(), json!(42));
176 }
177
178 #[test]
179 fn test_resolve_path_nested() {
180 let input = json!({"a": {"b": {"c": 99}}});
181 assert_eq!(resolve_path(&input, "$.a.b.c").unwrap(), json!(99));
182 }
183
184 #[test]
185 fn test_resolve_path_missing() {
186 let input = json!({"a": 1});
187 assert_eq!(
188 resolve_path(&input, "$.missing").unwrap_err(),
189 PathError::NoResults("$['missing']".into())
190 );
191 }
192
193 #[test]
194 fn test_resolve_path_array_index() {
195 let input = json!({"items": [10, 20, 30]});
196 assert_eq!(resolve_path(&input, "$.items[0]").unwrap(), json!(10));
197 assert_eq!(resolve_path(&input, "$.items[2]").unwrap(), json!(30));
198 }
199
200 #[test]
201 fn test_apply_input_path_default() {
202 let input = json!({"x": 1});
203 assert_eq!(apply_input_path(&input, None).unwrap(), input);
204 assert_eq!(apply_input_path(&input, Some("$")).unwrap(), input);
205 }
206
207 #[test]
208 fn test_apply_result_path_default() {
209 let input = json!({"x": 1});
210 let result = json!({"y": 2});
211 assert_eq!(apply_result_path(&input, &result, None), result);
213 assert_eq!(apply_result_path(&input, &result, Some("$")), result);
214 }
215
216 #[test]
217 fn test_apply_result_path_null() {
218 let input = json!({"x": 1});
219 let result = json!({"y": 2});
220 assert_eq!(apply_result_path(&input, &result, Some("null")), input);
222 }
223
224 #[test]
225 fn test_apply_result_path_nested() {
226 let input = json!({"x": 1});
227 let result = json!("hello");
228 let output = apply_result_path(&input, &result, Some("$.result"));
229 assert_eq!(output, json!({"x": 1, "result": "hello"}));
230 }
231
232 #[test]
233 fn test_set_at_path_non_object_intermediate() {
234 let input = json!({"x": 42});
237 let result = json!("hello");
238 let output = apply_result_path(&input, &result, Some("$.x.nested"));
239 assert_eq!(output, json!({"x": 42}));
241 }
242
243 #[test]
246 fn test_set_at_path_array_index_leaf() {
247 let input = json!({"a": [1, 2, 3]});
248 let result = json!(99);
249 let output = apply_result_path(&input, &result, Some("$.a[1]"));
250 assert_eq!(output, json!({"a": [1, 99, 3]}));
251 assert!(output.get("a[1]").is_none());
253 }
254
255 #[test]
256 fn test_set_at_path_array_index_then_field() {
257 let input = json!({"items": [{"v": 1}]});
258 let result = json!("done");
259 let output = apply_result_path(&input, &result, Some("$.items[0].status"));
260 assert_eq!(output, json!({"items": [{"v": 1, "status": "done"}]}));
261 }
262
263 #[test]
264 fn test_set_at_path_array_index_grows_array() {
265 let input = json!({});
266 let result = json!("x");
267 let output = apply_result_path(&input, &result, Some("$.a[2]"));
268 assert_eq!(output, json!({"a": [null, null, "x"]}));
269 }
270
271 #[test]
272 fn test_apply_output_path() {
273 let output = json!({"a": 1, "b": 2});
274 assert_eq!(apply_output_path(&output, Some("$.a")).unwrap(), json!(1));
275 assert_eq!(apply_output_path(&output, None).unwrap(), output);
276 }
277
278 #[test]
279 fn test_resolve_path_unclosed_bracket_does_not_panic() {
280 let input = json!({"arr": [1, 2, 3]});
283 assert!(matches!(
286 resolve_path(&input, "$.arr["),
287 Err(PathError::Invalid(_))
288 ));
289 }
290
291 #[test]
292 fn test_resolve_path_multibyte_after_bracket_does_not_panic() {
293 let input = json!({"x": [1, 2, 3]});
296 assert!(matches!(
297 resolve_path(&input, "$.x[é"),
298 Err(PathError::Invalid(_))
299 ));
300 assert!(matches!(
302 resolve_path(&input, "$.x[é]"),
303 Err(PathError::Invalid(_))
304 ));
305 }
306
307 #[test]
308 fn test_resolve_path_empty_brackets_do_not_panic() {
309 let input = json!({"x": [1, 2, 3]});
310 assert!(matches!(
311 resolve_path(&input, "$.x[]"),
312 Err(PathError::Invalid(_))
313 ));
314 }
315
316 #[test]
317 fn test_resolve_path_bracket_only_segment() {
318 let input = json!({"a": 1});
320 assert!(matches!(
321 resolve_path(&input, "$.["),
322 Err(PathError::Invalid(_))
323 ));
324 assert!(matches!(
325 resolve_path(&input, "$.]"),
326 Err(PathError::Invalid(_))
327 ));
328 }
329
330 #[test]
331 fn test_split_path_segments_well_formed_index_still_works() {
332 let input = json!({"items": [10, 20, 30]});
334 assert_eq!(resolve_path(&input, "$.items[1]").unwrap(), json!(20));
335 let nested = json!({"a": {"b": [{"c": 7}]}});
336 assert_eq!(resolve_path(&nested, "$.a.b[0].c").unwrap(), json!(7));
337 }
338
339 #[test]
340 fn test_apply_input_path_malformed_does_not_panic() {
341 let input = json!({"arr": [1, 2, 3]});
342 let (err, _) = apply_input_path(&input, Some("$.arr[")).unwrap_err();
344 assert_eq!(err, "States.Runtime");
345 assert!(apply_output_path(&input, Some("$.x[é")).is_err());
346 }
347}
348
349#[cfg(test)]
350mod missing_path_tests {
351 use super::*;
352 use serde_json::json;
353
354 #[test]
355 fn missing_input_path_is_states_runtime() {
356 let (err, cause) = apply_input_path(&json!({"a": 1}), Some("$.x")).unwrap_err();
357 assert_eq!(err, "States.Runtime");
358 assert_eq!(cause, "Invalid path '$.x' : No results for path: $['x']");
359 let (err, _) = apply_output_path(&json!({"a": 1}), Some("$.a.b")).unwrap_err();
360 assert_eq!(err, "States.Runtime");
361 assert_eq!(
363 apply_input_path(&json!({"a": null}), Some("$.a")).unwrap(),
364 Value::Null
365 );
366 }
367
368 #[test]
369 fn full_jsonpath_syntax_in_input_path() {
370 let input =
371 json!({"items": [{"id": 1, "ok": true}, {"id": 2, "ok": false}], "k": {"x y": 5}});
372 assert_eq!(
373 apply_input_path(&input, Some("$['k']['x y']")).unwrap(),
374 json!(5)
375 );
376 assert_eq!(
377 apply_input_path(&input, Some("$.items[-1].id")).unwrap(),
378 json!(2)
379 );
380 assert_eq!(
381 apply_input_path(&input, Some("$.items[*].id")).unwrap(),
382 json!([1, 2])
383 );
384 assert_eq!(
385 apply_input_path(&input, Some("$.items[?(@.ok == true)].id")).unwrap(),
386 json!([1])
387 );
388 assert_eq!(
389 apply_input_path(&input, Some("$.items[0:1]")).unwrap(),
390 json!([{"id": 1, "ok": true}])
391 );
392 }
393
394 #[test]
395 fn result_path_bracket_notation() {
396 let out = apply_result_path(&json!({"a": {}}), &json!(1), Some("$['a']['b c']"));
397 assert_eq!(out, json!({"a": {"b c": 1}}));
398 let out = apply_result_path(&json!({"a": [1, 2]}), &json!(9), Some("$.a[-1]"));
399 assert_eq!(out, json!({"a": [1, 9]}));
400 }
401
402 #[test]
403 fn context_paths() {
404 let ctx = json!({"Execution": {"Id": "arn"}});
405 assert_eq!(
406 resolve_with_context(&json!({}), Some(&ctx), "$$.Execution.Id").unwrap(),
407 json!("arn")
408 );
409 assert!(resolve_with_context(&json!({}), Some(&ctx), "$$.Nope").is_err());
410 assert_eq!(
411 resolve_with_context(&json!({"a": 1}), None, "$.a").unwrap(),
412 json!(1)
413 );
414 }
415}