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