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(
37 input: &Value,
38 result: &Value,
39 path: Option<&str>,
40) -> Result<Value, StatesError> {
41 match path {
42 None | Some("$") => Ok(result.clone()),
43 Some("null") => Ok(input.clone()),
44 Some(p) => set_at_path(input, p, result),
45 }
46}
47
48pub const REFERENCE_PATH_CONFLICT: &str = "States.ReferencePathConflict";
50
51fn reference_path_conflict(step: &Step, input: &Value) -> StatesError {
54 let step = match step {
55 Step::Name(name) => name.clone(),
56 Step::Index(index) => index.to_string(),
57 };
58 (
59 REFERENCE_PATH_CONFLICT.to_string(),
60 format!("Unable to apply step \"{step}\" to input {input}"),
61 )
62}
63
64pub fn resolve_path(root: &Value, path: &str) -> Result<Value, PathError> {
66 crate::jsonpath::evaluate(root, path)
67}
68
69pub fn resolve_reference(root: &Value, path: &str) -> Result<Value, StatesError> {
73 resolve_with_context(root, None, path).map_err(|e| runtime_error(path_error_cause(path, &e)))
74}
75
76pub fn resolve_reference_with_context(
79 input: &Value,
80 context: Option<&Value>,
81 path: &str,
82) -> Result<Value, StatesError> {
83 resolve_with_context(input, context, path)
84 .map_err(|e| runtime_error(path_error_cause(path, &e)))
85}
86
87pub fn resolve_with_context(
90 input: &Value,
91 context: Option<&Value>,
92 path: &str,
93) -> Result<Value, PathError> {
94 if let Some((name, within)) = crate::variables::split_reference(path) {
95 let value = crate::variables::lookup(name)
96 .ok_or_else(|| PathError::Invalid(format!("the variable '{name}' is not defined")))?;
97 return resolve_path(&value, &within);
98 }
99 match path.strip_prefix("$$") {
100 Some(rest) => match context {
101 Some(ctx) => resolve_path(ctx, &format!("${rest}")),
102 None => Ok(Value::Null),
103 },
104 None => resolve_path(input, path),
105 }
106}
107
108pub fn path_error_cause(path: &str, err: &PathError) -> String {
109 match err {
110 PathError::NoResults(norm) => {
111 format!("Invalid path '{path}' : No results for path: {norm}")
112 }
113 PathError::MissingProperty { property, parent } => {
116 format!("Invalid path '{path}' : Property {property} not found in path {parent}")
117 }
118 PathError::Invalid(msg) => format!("Invalid path '{path}' : {msg}"),
119 }
120}
121
122pub(crate) fn value_text(value: &Value) -> String {
124 match value {
125 Value::String(text) => text.clone(),
126 other => other.to_string(),
127 }
128}
129
130pub fn runtime_error(cause: String) -> StatesError {
131 ("States.Runtime".to_string(), cause)
132}
133
134fn set_at_path(root: &Value, path: &str, value: &Value) -> Result<Value, StatesError> {
139 let mut result = root.clone();
140 let Some(steps) = JsonPath::parse(path).ok().and_then(|p| p.reference_steps()) else {
141 return Ok(result);
142 };
143
144 fn container_for(next: &Step) -> Value {
146 match next {
147 Step::Name(_) => serde_json::json!({}),
148 Step::Index(_) => Value::Array(Vec::new()),
149 }
150 }
151
152 fn assign<'s>(current: &mut Value, steps: &'s [Step], value: &Value) -> Result<(), &'s Step> {
155 let Some((step, rest)) = steps.split_first() else {
156 *current = value.clone();
157 return Ok(());
158 };
159 match step {
160 Step::Name(name) => {
161 let Some(obj) = current.as_object_mut() else {
162 return Err(step);
163 };
164 match rest.first() {
165 None => {
166 obj.insert(name.clone(), value.clone());
167 Ok(())
168 }
169 Some(next) => {
170 let child = obj
171 .entry(name.clone())
172 .or_insert_with(|| container_for(next));
173 assign(child, rest, value)
174 }
175 }
176 }
177 Step::Index(idx) => {
178 let Some(arr) = current.as_array_mut() else {
179 return Err(step);
180 };
181 {
182 let idx = if *idx < 0 {
183 match usize::try_from(arr.len() as i64 + idx) {
184 Ok(i) => i,
185 Err(_) => return Err(step),
186 }
187 } else {
188 *idx as usize
189 };
190 if arr.len() <= idx {
191 arr.resize(idx + 1, Value::Null);
192 }
193 if let Some(next) = rest.first() {
194 if arr[idx].is_null() {
195 arr[idx] = container_for(next);
196 }
197 }
198 assign(&mut arr[idx], rest, value)
199 }
200 }
201 }
202 }
203
204 assign(&mut result, &steps, value).map_err(|step| reference_path_conflict(step, root))?;
205 Ok(result)
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use serde_json::json;
212
213 #[test]
214 fn test_resolve_path_root() {
215 let input = json!({"a": 1});
216 assert_eq!(resolve_path(&input, "$").unwrap(), input);
217 }
218
219 #[test]
220 fn test_resolve_path_simple_field() {
221 let input = json!({"name": "hello", "value": 42});
222 assert_eq!(resolve_path(&input, "$.name").unwrap(), json!("hello"));
223 assert_eq!(resolve_path(&input, "$.value").unwrap(), json!(42));
224 }
225
226 #[test]
227 fn test_resolve_path_nested() {
228 let input = json!({"a": {"b": {"c": 99}}});
229 assert_eq!(resolve_path(&input, "$.a.b.c").unwrap(), json!(99));
230 }
231
232 #[test]
233 fn test_resolve_path_missing() {
234 let input = json!({"a": 1});
235 let error = resolve_path(&input, "$.missing").unwrap_err();
237 assert_eq!(
238 path_error_cause("$.missing", &error),
239 "Invalid path '$.missing' : No results for path: $['missing']"
240 );
241 let error = resolve_path(&json!([{}]), "$.missing").unwrap_err();
242 assert_eq!(
243 path_error_cause("$.missing", &error),
244 "Invalid path '$.missing' : Property ['missing'] not found in path $"
245 );
246 }
247
248 #[test]
249 fn test_resolve_path_array_index() {
250 let input = json!({"items": [10, 20, 30]});
251 assert_eq!(resolve_path(&input, "$.items[0]").unwrap(), json!(10));
252 assert_eq!(resolve_path(&input, "$.items[2]").unwrap(), json!(30));
253 }
254
255 #[test]
256 fn test_apply_input_path_default() {
257 let input = json!({"x": 1});
258 assert_eq!(apply_input_path(&input, None).unwrap(), input);
259 assert_eq!(apply_input_path(&input, Some("$")).unwrap(), input);
260 }
261
262 #[test]
263 fn test_apply_result_path_default() {
264 let input = json!({"x": 1});
265 let result = json!({"y": 2});
266 assert_eq!(apply_result_path(&input, &result, None).unwrap(), result);
268 assert_eq!(
269 apply_result_path(&input, &result, Some("$")).unwrap(),
270 result
271 );
272 }
273
274 #[test]
275 fn test_apply_result_path_null() {
276 let input = json!({"x": 1});
277 let result = json!({"y": 2});
278 assert_eq!(
280 apply_result_path(&input, &result, Some("null")).unwrap(),
281 input
282 );
283 }
284
285 #[test]
286 fn test_apply_result_path_nested() {
287 let input = json!({"x": 1});
288 let result = json!("hello");
289 let output = apply_result_path(&input, &result, Some("$.result")).unwrap();
290 assert_eq!(output, json!({"x": 1, "result": "hello"}));
291 }
292
293 #[test]
294 fn test_set_at_path_non_object_intermediate() {
295 let input = json!({"x": 42});
298 let result = json!("hello");
299 let (error, cause) = apply_result_path(&input, &result, Some("$.x.nested")).unwrap_err();
300 assert_eq!(error, REFERENCE_PATH_CONFLICT);
301 assert_eq!(cause, r#"Unable to apply step "nested" to input {"x":42}"#);
302 }
303
304 #[test]
307 fn test_set_at_path_array_index_leaf() {
308 let input = json!({"a": [1, 2, 3]});
309 let result = json!(99);
310 let output = apply_result_path(&input, &result, Some("$.a[1]")).unwrap();
311 assert_eq!(output, json!({"a": [1, 99, 3]}));
312 assert!(output.get("a[1]").is_none());
314 }
315
316 #[test]
317 fn test_set_at_path_array_index_then_field() {
318 let input = json!({"items": [{"v": 1}]});
319 let result = json!("done");
320 let output = apply_result_path(&input, &result, Some("$.items[0].status")).unwrap();
321 assert_eq!(output, json!({"items": [{"v": 1, "status": "done"}]}));
322 }
323
324 #[test]
325 fn test_set_at_path_array_index_grows_array() {
326 let input = json!({});
327 let result = json!("x");
328 let output = apply_result_path(&input, &result, Some("$.a[2]")).unwrap();
329 assert_eq!(output, json!({"a": [null, null, "x"]}));
330 }
331
332 #[test]
333 fn test_apply_output_path() {
334 let output = json!({"a": 1, "b": 2});
335 assert_eq!(apply_output_path(&output, Some("$.a")).unwrap(), json!(1));
336 assert_eq!(apply_output_path(&output, None).unwrap(), output);
337 }
338
339 #[test]
340 fn test_resolve_path_unclosed_bracket_does_not_panic() {
341 let input = json!({"arr": [1, 2, 3]});
344 assert!(matches!(
347 resolve_path(&input, "$.arr["),
348 Err(PathError::Invalid(_))
349 ));
350 }
351
352 #[test]
353 fn test_resolve_path_multibyte_after_bracket_does_not_panic() {
354 let input = json!({"x": [1, 2, 3]});
357 assert!(matches!(
358 resolve_path(&input, "$.x[é"),
359 Err(PathError::Invalid(_))
360 ));
361 assert!(matches!(
363 resolve_path(&input, "$.x[é]"),
364 Err(PathError::Invalid(_))
365 ));
366 }
367
368 #[test]
369 fn test_resolve_path_empty_brackets_do_not_panic() {
370 let input = json!({"x": [1, 2, 3]});
371 assert!(matches!(
372 resolve_path(&input, "$.x[]"),
373 Err(PathError::Invalid(_))
374 ));
375 }
376
377 #[test]
378 fn test_resolve_path_bracket_only_segment() {
379 let input = json!({"a": 1});
381 assert!(matches!(
382 resolve_path(&input, "$.["),
383 Err(PathError::Invalid(_))
384 ));
385 assert!(matches!(
386 resolve_path(&input, "$.]"),
387 Err(PathError::Invalid(_))
388 ));
389 }
390
391 #[test]
392 fn test_split_path_segments_well_formed_index_still_works() {
393 let input = json!({"items": [10, 20, 30]});
395 assert_eq!(resolve_path(&input, "$.items[1]").unwrap(), json!(20));
396 let nested = json!({"a": {"b": [{"c": 7}]}});
397 assert_eq!(resolve_path(&nested, "$.a.b[0].c").unwrap(), json!(7));
398 }
399
400 #[test]
401 fn test_apply_input_path_malformed_does_not_panic() {
402 let input = json!({"arr": [1, 2, 3]});
403 let (err, _) = apply_input_path(&input, Some("$.arr[")).unwrap_err();
405 assert_eq!(err, "States.Runtime");
406 assert!(apply_output_path(&input, Some("$.x[é")).is_err());
407 }
408}
409
410#[cfg(test)]
411mod missing_path_tests {
412 use super::*;
413 use serde_json::json;
414
415 #[test]
416 fn missing_input_path_is_states_runtime() {
417 let (err, cause) = apply_input_path(&json!({"a": 1}), Some("$.x")).unwrap_err();
418 assert_eq!(err, "States.Runtime");
419 assert_eq!(cause, "Invalid path '$.x' : No results for path: $['x']");
420 let (err, _) = apply_output_path(&json!({"a": 1}), Some("$.a.b")).unwrap_err();
421 assert_eq!(err, "States.Runtime");
422 assert_eq!(
424 apply_input_path(&json!({"a": null}), Some("$.a")).unwrap(),
425 Value::Null
426 );
427 }
428
429 #[test]
430 fn full_jsonpath_syntax_in_input_path() {
431 let input =
432 json!({"items": [{"id": 1, "ok": true}, {"id": 2, "ok": false}], "k": {"x y": 5}});
433 assert_eq!(
434 apply_input_path(&input, Some("$['k']['x y']")).unwrap(),
435 json!(5)
436 );
437 assert_eq!(
438 apply_input_path(&input, Some("$.items[-1].id")).unwrap(),
439 json!(2)
440 );
441 assert_eq!(
442 apply_input_path(&input, Some("$.items[*].id")).unwrap(),
443 json!([1, 2])
444 );
445 assert_eq!(
446 apply_input_path(&input, Some("$.items[?(@.ok == true)].id")).unwrap(),
447 json!([1])
448 );
449 assert_eq!(
450 apply_input_path(&input, Some("$.items[0:1]")).unwrap(),
451 json!([{"id": 1, "ok": true}])
452 );
453 }
454
455 #[test]
456 fn result_path_bracket_notation() {
457 let out = apply_result_path(&json!({"a": {}}), &json!(1), Some("$['a']['b c']")).unwrap();
458 assert_eq!(out, json!({"a": {"b c": 1}}));
459 let out = apply_result_path(&json!({"a": [1, 2]}), &json!(9), Some("$.a[-1]")).unwrap();
460 assert_eq!(out, json!({"a": [1, 9]}));
461 }
462
463 #[test]
464 fn context_paths() {
465 let ctx = json!({"Execution": {"Id": "arn"}});
466 assert_eq!(
467 resolve_with_context(&json!({}), Some(&ctx), "$$.Execution.Id").unwrap(),
468 json!("arn")
469 );
470 assert!(resolve_with_context(&json!({}), Some(&ctx), "$$.Nope").is_err());
471 assert_eq!(
472 resolve_with_context(&json!({"a": 1}), None, "$.a").unwrap(),
473 json!(1)
474 );
475 }
476}
477
478#[cfg(test)]
479mod reference_path_conflict_tests {
480 use super::*;
481 use serde_json::json;
482
483 #[test]
485 fn a_step_into_a_scalar_is_a_conflict() {
486 assert_eq!(
487 apply_result_path(&json!({"a": 1}), &json!([{}]), Some("$.a.b")).unwrap_err(),
488 (
489 "States.ReferencePathConflict".to_string(),
490 r#"Unable to apply step "b" to input {"a":1}"#.to_string()
491 )
492 );
493 assert_eq!(
495 apply_result_path(&json!({"a": {}}), &json!(1), Some("$.a[0]"))
496 .unwrap_err()
497 .0,
498 REFERENCE_PATH_CONFLICT
499 );
500 assert!(apply_result_path(&json!(5), &json!(1), Some("$.x")).is_err());
501 assert_eq!(
503 apply_result_path(&json!({}), &json!(1), Some("$.a.b")).unwrap(),
504 json!({"a": {"b": 1}})
505 );
506 }
507}