fakecloud_stepfunctions/
error_handling.rs1use serde_json::Value;
2
3pub fn find_catcher(catchers: &[Value], error: &str) -> Option<(String, Option<String>)> {
5 for catcher in catchers {
6 let error_equals = match catcher["ErrorEquals"].as_array() {
7 Some(arr) => arr,
8 None => continue,
9 };
10
11 let matches = error_equals.iter().any(|e| {
12 let pattern = e.as_str().unwrap_or("");
13 pattern == "States.ALL" || pattern == error
14 });
15
16 if matches {
17 let next = catcher["Next"].as_str()?.to_string();
18 let result_path = if catcher.get("ResultPath").is_some_and(|v| v.is_null()) {
20 Some("null".to_string())
21 } else {
22 catcher["ResultPath"].as_str().map(|s| s.to_string())
23 };
24 return Some((next, result_path));
25 }
26 }
27 None
28}
29
30pub fn should_retry(retriers: &[Value], error: &str, attempt: u32) -> Option<u64> {
33 for retrier in retriers {
34 let error_equals = match retrier["ErrorEquals"].as_array() {
35 Some(arr) => arr,
36 None => continue,
37 };
38
39 let matches = error_equals.iter().any(|e| {
40 let pattern = e.as_str().unwrap_or("");
41 pattern == "States.ALL" || pattern == error
42 });
43
44 if matches {
45 let max_attempts = retrier["MaxAttempts"].as_u64().unwrap_or(3) as u32;
46 if attempt >= max_attempts {
47 return None;
48 }
49
50 let interval_seconds = retrier["IntervalSeconds"].as_f64().unwrap_or(1.0);
51 let backoff_rate = retrier["BackoffRate"].as_f64().unwrap_or(2.0);
52 let max_delay = retrier["MaxDelaySeconds"].as_f64().unwrap_or(60.0);
53
54 let delay = interval_seconds * backoff_rate.powi(attempt as i32);
55 let delay = delay.min(max_delay);
56
57 return Some((delay * 1000.0) as u64);
58 }
59 }
60 None
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use serde_json::json;
67
68 #[test]
69 fn test_find_catcher_exact_match() {
70 let catchers = vec![json!({
71 "ErrorEquals": ["CustomError"],
72 "Next": "HandleError"
73 })];
74 let result = find_catcher(&catchers, "CustomError");
75 assert_eq!(result, Some(("HandleError".to_string(), None)));
76 }
77
78 #[test]
79 fn test_find_catcher_states_all() {
80 let catchers = vec![json!({
81 "ErrorEquals": ["States.ALL"],
82 "Next": "CatchAll"
83 })];
84 let result = find_catcher(&catchers, "AnyError");
85 assert_eq!(result, Some(("CatchAll".to_string(), None)));
86 }
87
88 #[test]
89 fn test_find_catcher_no_match() {
90 let catchers = vec![json!({
91 "ErrorEquals": ["SpecificError"],
92 "Next": "Handle"
93 })];
94 let result = find_catcher(&catchers, "DifferentError");
95 assert_eq!(result, None);
96 }
97
98 #[test]
99 fn test_find_catcher_with_result_path() {
100 let catchers = vec![json!({
101 "ErrorEquals": ["States.ALL"],
102 "Next": "Handle",
103 "ResultPath": "$.error"
104 })];
105 let result = find_catcher(&catchers, "AnyError");
106 assert_eq!(
107 result,
108 Some(("Handle".to_string(), Some("$.error".to_string())))
109 );
110 }
111
112 #[test]
113 fn test_should_retry_first_attempt() {
114 let retriers = vec![json!({
115 "ErrorEquals": ["States.ALL"],
116 "IntervalSeconds": 1,
117 "MaxAttempts": 3,
118 "BackoffRate": 2.0
119 })];
120 let result = should_retry(&retriers, "AnyError", 0);
121 assert_eq!(result, Some(1000)); }
123
124 #[test]
125 fn test_should_retry_second_attempt() {
126 let retriers = vec![json!({
127 "ErrorEquals": ["States.ALL"],
128 "IntervalSeconds": 1,
129 "MaxAttempts": 3,
130 "BackoffRate": 2.0
131 })];
132 let result = should_retry(&retriers, "AnyError", 1);
133 assert_eq!(result, Some(2000)); }
135
136 #[test]
137 fn test_should_retry_exhausted() {
138 let retriers = vec![json!({
139 "ErrorEquals": ["States.ALL"],
140 "MaxAttempts": 2
141 })];
142 let result = should_retry(&retriers, "AnyError", 2);
143 assert_eq!(result, None);
144 }
145
146 #[test]
147 fn test_should_retry_no_match() {
148 let retriers = vec![json!({
149 "ErrorEquals": ["SpecificError"],
150 "MaxAttempts": 3
151 })];
152 let result = should_retry(&retriers, "DifferentError", 0);
153 assert_eq!(result, None);
154 }
155}