1use jules_core::errors::SDKError;
4
5pub trait RetryPolicy: Send + Sync {
7 fn should_retry(&self, attempt: u32, error: &SDKError) -> Option<u64>;
10}
11
12#[derive(Debug, Clone)]
14pub struct ExponentialBackoff {
15 pub max_retries: u32,
17 pub base_delay_ms: u64,
19 pub max_delay_ms: u64,
21}
22
23impl Default for ExponentialBackoff {
24 fn default() -> Self {
25 Self {
26 max_retries: 3,
27 base_delay_ms: 100,
28 max_delay_ms: 10_000,
29 }
30 }
31}
32
33impl RetryPolicy for ExponentialBackoff {
34 fn should_retry(&self, attempt: u32, error: &SDKError) -> Option<u64> {
35 if attempt >= self.max_retries {
36 return None;
37 }
38
39 let is_retryable = match error {
40 SDKError::Api(api_err) => {
41 if let Some(status) = api_err.status_code {
43 status == 429 || (500..=599).contains(&status)
44 } else {
45 false
46 }
47 }
48 SDKError::Network(_) => true,
49 _ => false,
50 };
51
52 if is_retryable {
53 let mut delay = self.base_delay_ms.saturating_mul(1 << attempt);
56 if delay > self.max_delay_ms {
57 delay = self.max_delay_ms;
58 }
59 Some(delay)
60 } else {
61 None
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use jules_core::errors::{ApiError, NetworkError};
70
71 #[test]
72 fn test_exponential_backoff_max_retries() {
73 let policy = ExponentialBackoff {
74 max_retries: 2,
75 ..Default::default()
76 };
77
78 let error = SDKError::Network(NetworkError::new("Timeout"));
79
80 assert_eq!(policy.should_retry(0, &error), Some(100));
82 assert_eq!(policy.should_retry(1, &error), Some(200));
84 assert_eq!(policy.should_retry(2, &error), None);
86 }
87
88 #[test]
89 fn test_exponential_backoff_rate_limit() {
90 let policy = ExponentialBackoff::default();
91 let error = SDKError::Api(ApiError::with_status("Rate Limit Exceeded", 429));
92
93 assert_eq!(policy.should_retry(0, &error), Some(100));
94 }
95
96 #[test]
97 fn test_exponential_backoff_server_error() {
98 let policy = ExponentialBackoff::default();
99 let error = SDKError::Api(ApiError::with_status("Internal Server Error", 500));
100
101 assert_eq!(policy.should_retry(0, &error), Some(100));
102 }
103
104 #[test]
105 fn test_exponential_backoff_bad_request() {
106 let policy = ExponentialBackoff::default();
107 let error = SDKError::Api(ApiError::with_status("Bad Request", 400));
108
109 assert_eq!(policy.should_retry(0, &error), None);
111 }
112
113 #[test]
114 fn test_exponential_backoff_no_status_code() {
115 let policy = ExponentialBackoff::default();
116 let error = SDKError::Api(ApiError::new("Unknown Error"));
117
118 assert_eq!(policy.should_retry(0, &error), None);
119 }
120}