Skip to main content

swf_core/models/
retry.rs

1use crate::models::duration::*;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5
6/// Represents the definition of a retry policy
7#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
8pub struct RetryPolicyDefinition {
9    /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub when: Option<String>,
12
13    /// Gets/sets a runtime expression used to determine whether or not to retry running the task, in a given context
14    #[serde(rename = "exceptWhen", skip_serializing_if = "Option::is_none")]
15    pub except_when: Option<String>,
16
17    /// Gets/sets the limits, if any, of the retry policy
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub limit: Option<RetryPolicyLimitDefinition>,
20
21    /// Gets/sets the delay duration between retry attempts
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub delay: Option<OneOfDurationOrIso8601Expression>,
24
25    /// Gets/sets the backoff strategy to use, if any
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub backoff: Option<BackoffStrategyDefinition>,
28
29    /// Gets/sets the parameters, if any, that control the randomness or variability of the delay between retry attempts
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub jitter: Option<JitterDefinition>,
32}
33
34/// Represents the configuration of the limits of a retry policy
35#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct RetryPolicyLimitDefinition {
37    /// Gets/sets the definition of the limits for all retry attempts of a given policy
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub attempt: Option<RetryAttemptLimitDefinition>,
40
41    /// Gets/sets the maximum duration, if any, during which to retry a given task
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub duration: Option<OneOfDurationOrIso8601Expression>,
44}
45
46/// Represents the definition of the limits for all retry attempts of a given policy
47#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct RetryAttemptLimitDefinition {
49    /// Gets/sets the maximum attempts count
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub count: Option<u16>,
52
53    /// Gets/sets the duration limit, if any, for all retry attempts
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub duration: Option<OneOfDurationOrIso8601Expression>,
56}
57
58/// Represents the definition of a retry backoff strategy
59#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
60pub struct BackoffStrategyDefinition {
61    /// Gets/sets the definition of the constant backoff to use, if any
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub constant: Option<ConstantBackoffDefinition>,
64
65    /// Gets/sets the definition of the exponential backoff to use, if any
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub exponential: Option<ExponentialBackoffDefinition>,
68
69    /// Gets/sets the definition of the linear backoff to use, if any
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub linear: Option<LinearBackoffDefinition>,
72}
73
74/// Represents the definition of a constant backoff
75#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
76pub struct ConstantBackoffDefinition {
77    /// Gets/sets the definition of the constant backoff parameters (e.g., {"factor": 2})
78    #[serde(flatten, skip_serializing_if = "Option::is_none")]
79    pub definition: Option<HashMap<String, Value>>,
80}
81impl ConstantBackoffDefinition {
82    /// Creates a constant backoff with a delay duration
83    pub fn with_delay(delay: &str) -> Self {
84        let mut map = HashMap::new();
85        map.insert("delay".to_string(), Value::String(delay.to_string()));
86        Self {
87            definition: Some(map),
88        }
89    }
90
91    /// Gets the delay value, if set
92    pub fn delay(&self) -> Option<&str> {
93        self.definition.as_ref()?.get("delay")?.as_str()
94    }
95}
96
97/// Represents the definition of an exponential backoff
98#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
99pub struct ExponentialBackoffDefinition {
100    /// Gets/sets the definition of the exponential backoff parameters (e.g., {"factor": 2, "maxDelay": "PT30S"})
101    #[serde(flatten, skip_serializing_if = "Option::is_none")]
102    pub definition: Option<HashMap<String, Value>>,
103}
104impl ExponentialBackoffDefinition {
105    /// Creates an exponential backoff with a factor
106    pub fn with_factor(factor: f64) -> Self {
107        let mut map = HashMap::new();
108        map.insert("factor".to_string(), Value::from(factor));
109        Self {
110            definition: Some(map),
111        }
112    }
113
114    /// Creates an exponential backoff with a factor and max delay
115    pub fn with_factor_and_max_delay(factor: f64, max_delay: &str) -> Self {
116        let mut map = HashMap::new();
117        map.insert("factor".to_string(), Value::from(factor));
118        map.insert("maxDelay".to_string(), Value::String(max_delay.to_string()));
119        Self {
120            definition: Some(map),
121        }
122    }
123
124    /// Gets the factor value, if set
125    pub fn factor(&self) -> Option<f64> {
126        self.definition.as_ref()?.get("factor")?.as_f64()
127    }
128
129    /// Gets the maxDelay value, if set
130    pub fn max_delay(&self) -> Option<&str> {
131        self.definition.as_ref()?.get("maxDelay")?.as_str()
132    }
133}
134
135/// Represents the definition of a linear backoff
136#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
137pub struct LinearBackoffDefinition {
138    /// Gets/sets the linear incrementation to the delay between retry attempts
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub increment: Option<Duration>,
141
142    /// Gets/sets the definition of additional linear backoff parameters (e.g., {"maxDelay": "PT30S"})
143    #[serde(flatten, skip_serializing_if = "Option::is_none")]
144    pub definition: Option<HashMap<String, Value>>,
145}
146
147/// Represents the definition of the parameters that control the randomness or variability of a delay, typically between retry attempts
148#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct JitterDefinition {
150    /// Gets/sets the minimum duration of the jitter range
151    pub from: Duration,
152
153    /// Gets/sets the maximum duration of the jitter range
154    pub to: Duration,
155}
156
157define_one_of_or_reference!(
158    /// Represents a value that can be either a RetryPolicyDefinition or a reference to a RetryPolicyDefinition
159    OneOfRetryPolicyDefinitionOrReference, Retry(Box<RetryPolicyDefinition>)
160);
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_retry_policy_serialize() {
168        let policy = RetryPolicyDefinition {
169            when: Some("${someCondition}".to_string()),
170            except_when: Some("${someOtherCondition}".to_string()),
171            delay: Some(OneOfDurationOrIso8601Expression::Duration(
172                Duration::from_seconds(5),
173            )),
174            backoff: Some(BackoffStrategyDefinition {
175                exponential: Some(ExponentialBackoffDefinition::with_factor(2.0)),
176                constant: None,
177                linear: None,
178            }),
179            limit: Some(RetryPolicyLimitDefinition {
180                attempt: Some(RetryAttemptLimitDefinition {
181                    count: Some(3),
182                    duration: Some(OneOfDurationOrIso8601Expression::Duration(
183                        Duration::from_minutes(1),
184                    )),
185                }),
186                duration: Some(OneOfDurationOrIso8601Expression::Duration(
187                    Duration::from_minutes(10),
188                )),
189            }),
190            jitter: Some(JitterDefinition {
191                from: Duration::from_seconds(1),
192                to: Duration::from_seconds(3),
193            }),
194        };
195
196        let json = serde_json::to_string(&policy).unwrap();
197        assert!(json.contains("\"when\":\"${someCondition}\""));
198        assert!(json.contains("\"exceptWhen\":\"${someOtherCondition}\""));
199        assert!(json.contains("\"factor\":2"));
200        assert!(json.contains("\"count\":3"));
201    }
202
203    #[test]
204    fn test_retry_policy_deserialize_inline() {
205        let json = r#"{
206            "when": "${someCondition}",
207            "exceptWhen": "${someOtherCondition}",
208            "delay": {"seconds": 5},
209            "backoff": {"exponential": {"factor": 2}},
210            "limit": {
211                "attempt": {"count": 3, "duration": {"minutes": 1}},
212                "duration": {"minutes": 10}
213            },
214            "jitter": {"from": {"seconds": 1}, "to": {"seconds": 3}}
215        }"#;
216
217        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
218        assert_eq!(policy.when, Some("${someCondition}".to_string()));
219        assert_eq!(
220            policy.except_when,
221            Some("${someOtherCondition}".to_string())
222        );
223        assert!(policy.delay.is_some());
224        assert!(policy.backoff.is_some());
225        assert!(policy.backoff.as_ref().unwrap().exponential.is_some());
226        assert_eq!(
227            policy
228                .backoff
229                .as_ref()
230                .unwrap()
231                .exponential
232                .as_ref()
233                .unwrap()
234                .factor(),
235            Some(2.0)
236        );
237        assert!(policy.limit.is_some());
238        assert_eq!(
239            policy
240                .limit
241                .as_ref()
242                .unwrap()
243                .attempt
244                .as_ref()
245                .unwrap()
246                .count,
247            Some(3)
248        );
249        assert!(policy.jitter.is_some());
250    }
251
252    #[test]
253    fn test_oneof_retry_reference_deserialize() {
254        let json = r#""default""#;
255        let oneof: OneOfRetryPolicyDefinitionOrReference = serde_json::from_str(json).unwrap();
256        match oneof {
257            OneOfRetryPolicyDefinitionOrReference::Reference(name) => {
258                assert_eq!(name, "default");
259            }
260            _ => panic!("Expected Reference variant"),
261        }
262    }
263
264    #[test]
265    fn test_oneof_retry_inline_deserialize() {
266        let json = r#"{"delay": {"seconds": 3}, "limit": {"attempt": {"count": 5}}}"#;
267        let oneof: OneOfRetryPolicyDefinitionOrReference = serde_json::from_str(json).unwrap();
268        match oneof {
269            OneOfRetryPolicyDefinitionOrReference::Retry(policy) => {
270                assert!(policy.delay.is_some());
271                assert_eq!(
272                    policy
273                        .limit
274                        .as_ref()
275                        .unwrap()
276                        .attempt
277                        .as_ref()
278                        .unwrap()
279                        .count,
280                    Some(5)
281                );
282            }
283            _ => panic!("Expected Retry variant"),
284        }
285    }
286
287    #[test]
288    fn test_constant_backoff_definition() {
289        let constant = ConstantBackoffDefinition::with_delay("PT5S");
290        assert_eq!(constant.delay(), Some("PT5S"));
291
292        let empty = ConstantBackoffDefinition::default();
293        assert_eq!(empty.delay(), None);
294    }
295
296    #[test]
297    fn test_exponential_backoff_definition() {
298        let exp = ExponentialBackoffDefinition::with_factor(2.5);
299        assert_eq!(exp.factor(), Some(2.5));
300        assert_eq!(exp.max_delay(), None);
301
302        let exp_with_max = ExponentialBackoffDefinition::with_factor_and_max_delay(2.0, "PT30S");
303        assert_eq!(exp_with_max.factor(), Some(2.0));
304        assert_eq!(exp_with_max.max_delay(), Some("PT30S"));
305    }
306
307    #[test]
308    fn test_backoff_strategy_serialize() {
309        let backoff = BackoffStrategyDefinition {
310            constant: None,
311            exponential: Some(ExponentialBackoffDefinition::with_factor(2.0)),
312            linear: Some(LinearBackoffDefinition {
313                increment: Some(Duration::from_milliseconds(500)),
314                definition: None,
315            }),
316        };
317
318        let json = serde_json::to_string(&backoff).unwrap();
319        assert!(json.contains("\"exponential\""));
320        assert!(json.contains("\"factor\":2"));
321        assert!(json.contains("\"linear\""));
322        assert!(json.contains("\"increment\""));
323    }
324
325    #[test]
326    fn test_backoff_strategy_deserialize() {
327        let json = r#"{"exponential": {"factor": 2.5, "maxDelay": "PT30S"}}"#;
328        let backoff: BackoffStrategyDefinition = serde_json::from_str(json).unwrap();
329        assert!(backoff.exponential.is_some());
330        assert_eq!(backoff.exponential.as_ref().unwrap().factor(), Some(2.5));
331        assert_eq!(
332            backoff.exponential.as_ref().unwrap().max_delay(),
333            Some("PT30S")
334        );
335    }
336
337    #[test]
338    fn test_retry_attempt_limit_default() {
339        let limit = RetryAttemptLimitDefinition::default();
340        assert_eq!(limit.count, None);
341        assert_eq!(limit.duration, None);
342    }
343
344    #[test]
345    fn test_jitter_definition() {
346        let jitter = JitterDefinition {
347            from: Duration::from_seconds(1),
348            to: Duration::from_seconds(3),
349        };
350        assert_eq!(jitter.from.total_milliseconds(), 1000);
351        assert_eq!(jitter.to.total_milliseconds(), 3000);
352    }
353
354    // Additional tests matching Go SDK's task_try_test.go
355
356    #[test]
357    fn test_retry_policy_roundtrip_with_iso8601() {
358        // Matches Go SDK's TestRetryPolicy_MarshalJSON/UnmarshalJSON
359        let json = r#"{
360            "when": "${someCondition}",
361            "exceptWhen": "${someOtherCondition}",
362            "delay": {"seconds": 5},
363            "backoff": {"exponential": {"factor": 2}},
364            "limit": {
365                "attempt": {"count": 3, "duration": {"minutes": 1}},
366                "duration": {"minutes": 10}
367            },
368            "jitter": {"from": {"seconds": 1}, "to": {"seconds": 3}}
369        }"#;
370        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
371        assert_eq!(policy.when, Some("${someCondition}".to_string()));
372        assert_eq!(
373            policy.except_when,
374            Some("${someOtherCondition}".to_string())
375        );
376        assert!(policy.delay.is_some());
377        assert!(policy.delay.as_ref().unwrap().is_duration());
378        match policy.delay.as_ref().unwrap() {
379            OneOfDurationOrIso8601Expression::Duration(d) => assert_eq!(d.seconds, Some(5)),
380            _ => panic!("Expected Duration variant"),
381        }
382        // roundtrip
383        let serialized = serde_json::to_string(&policy).unwrap();
384        let deserialized: RetryPolicyDefinition = serde_json::from_str(&serialized).unwrap();
385        assert_eq!(policy, deserialized);
386    }
387
388    #[test]
389    fn test_retry_policy_constant_backoff_roundtrip() {
390        let json = r#"{
391            "delay": {"seconds": 3},
392            "backoff": {"constant": {"delay": "PT5S"}},
393            "limit": {"attempt": {"count": 5}}
394        }"#;
395        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
396        assert!(policy.backoff.as_ref().unwrap().constant.is_some());
397        let serialized = serde_json::to_string(&policy).unwrap();
398        let deserialized: RetryPolicyDefinition = serde_json::from_str(&serialized).unwrap();
399        assert_eq!(policy, deserialized);
400    }
401
402    #[test]
403    fn test_retry_policy_linear_backoff_deserialize() {
404        let json = r#"{
405            "delay": {"seconds": 1},
406            "backoff": {"linear": {"increment": {"seconds": 2}}},
407            "limit": {"attempt": {"count": 3}}
408        }"#;
409        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
410        assert!(policy.backoff.as_ref().unwrap().linear.is_some());
411        let linear = policy.backoff.as_ref().unwrap().linear.as_ref().unwrap();
412        assert!(linear.increment.is_some());
413    }
414
415    #[test]
416    fn test_oneof_retry_reference_roundtrip() {
417        let ref_val = OneOfRetryPolicyDefinitionOrReference::Reference("default".to_string());
418        let serialized = serde_json::to_string(&ref_val).unwrap();
419        assert_eq!(serialized, r#""default""#);
420        let deserialized: OneOfRetryPolicyDefinitionOrReference =
421            serde_json::from_str(&serialized).unwrap();
422        assert_eq!(ref_val, deserialized);
423    }
424
425    #[test]
426    fn test_oneof_retry_inline_roundtrip() {
427        let json = r#"{"delay": {"seconds": 3}, "backoff": {"exponential": {}}, "limit": {"attempt": {"count": 5}}}"#;
428        let oneof: OneOfRetryPolicyDefinitionOrReference = serde_json::from_str(json).unwrap();
429        match &oneof {
430            OneOfRetryPolicyDefinitionOrReference::Retry(policy) => {
431                assert!(policy.delay.is_some());
432                assert!(policy.backoff.is_some());
433            }
434            _ => panic!("Expected Retry variant"),
435        }
436        let serialized = serde_json::to_string(&oneof).unwrap();
437        let deserialized: OneOfRetryPolicyDefinitionOrReference =
438            serde_json::from_str(&serialized).unwrap();
439        assert_eq!(oneof, deserialized);
440    }
441
442    #[test]
443    fn test_retry_limit_duration_only() {
444        let json = r#"{"limit": {"duration": {"minutes": 5}}}"#;
445        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
446        assert!(policy.limit.is_some());
447        assert!(policy.limit.as_ref().unwrap().duration.is_some());
448        assert!(policy.limit.as_ref().unwrap().attempt.is_none());
449    }
450
451    #[test]
452    fn test_retry_policy_minimal() {
453        // Minimal retry with just delay
454        let json = r#"{"delay": {"seconds": 1}}"#;
455        let policy: RetryPolicyDefinition = serde_json::from_str(json).unwrap();
456        assert!(policy.delay.is_some());
457        assert!(policy.when.is_none());
458        assert!(policy.except_when.is_none());
459        assert!(policy.backoff.is_none());
460        assert!(policy.limit.is_none());
461        assert!(policy.jitter.is_none());
462    }
463
464    #[test]
465    fn test_retry_policy_default() {
466        let policy = RetryPolicyDefinition::default();
467        assert!(policy.when.is_none());
468        assert!(policy.except_when.is_none());
469        assert!(policy.delay.is_none());
470        assert!(policy.backoff.is_none());
471        assert!(policy.limit.is_none());
472        assert!(policy.jitter.is_none());
473    }
474}