1use std::time::Duration;
12
13#[derive(Clone, Debug, PartialEq)]
17pub enum ResiliencePolicy {
18 None,
20
21 Retry {
23 max_attempts: u32,
24 backoff: BackoffStrategy,
25 },
26
27 CircuitBreaker {
29 failure_threshold: u32,
30 recovery_timeout: Duration,
31 success_threshold: u32,
32 },
33
34 RateLimit {
36 requests_per_second: u32,
37 burst_capacity: u32,
38 },
39
40 Timeout { duration: Duration },
42
43 Combined { policies: Vec<ResiliencePolicy> },
45}
46
47#[derive(Clone, Debug, PartialEq)]
49pub enum BackoffStrategy {
50 Fixed { delay: Duration },
52
53 Exponential {
55 initial_delay: Duration,
56 multiplier: f64,
57 max_delay: Option<Duration>,
58 jitter: bool,
59 },
60
61 Linear {
63 initial_delay: Duration,
64 increment: Duration,
65 max_delay: Option<Duration>,
66 },
67}
68
69impl Default for BackoffStrategy {
70 fn default() -> Self {
71 Self::Exponential {
72 initial_delay: Duration::from_millis(100),
73 multiplier: 2.0,
74 max_delay: Some(Duration::from_secs(30)),
75 jitter: true,
76 }
77 }
78}
79
80#[derive(thiserror::Error, Debug, Clone, PartialEq)]
84pub enum ResilienceDomainError {
85 #[error("Operation timed out after {duration:?}")]
86 Timeout { duration: Duration },
87
88 #[error("Operation failed after {attempts} attempts")]
89 RetryExhausted { attempts: u32, last_error: String },
90
91 #[error("Circuit breaker is open - service unavailable")]
92 CircuitOpen,
93
94 #[error("Rate limit exceeded - too many requests")]
95 RateLimited { retry_after: Option<Duration> },
96
97 #[error("Operation cancelled")]
98 Cancelled,
99
100 #[error("Infrastructure error: {message}")]
101 Infrastructure { message: String },
102}
103
104impl ResilienceDomainError {
105 pub fn is_retryable(&self) -> bool {
107 match self {
108 Self::Timeout { .. } => true,
109 Self::RetryExhausted { .. } => false, Self::CircuitOpen => false, Self::RateLimited { .. } => true, Self::Cancelled => false, Self::Infrastructure { .. } => true, }
115 }
116
117 pub fn is_service_unavailable(&self) -> bool {
119 matches!(self, Self::CircuitOpen)
120 }
121
122 pub fn retry_after(&self) -> Option<Duration> {
124 match self {
125 Self::RateLimited { retry_after } => *retry_after,
126 _ => None,
127 }
128 }
129}
130
131#[async_trait::async_trait]
135pub trait ResilientOperation<T, E> {
136 fn resilience_policy(&self) -> ResiliencePolicy;
138
139 async fn execute(&self) -> Result<T, E>;
141
142 fn operation_id(&self) -> &str {
145 std::any::type_name::<Self>()
146 }
147
148 fn is_critical(&self) -> bool {
150 true
151 }
152}
153
154#[async_trait::async_trait]
158pub trait ResilientService {
159 fn default_resilience_policy(&self) -> ResiliencePolicy {
161 ResiliencePolicy::None
162 }
163
164 fn operation_policies(&self) -> std::collections::HashMap<String, ResiliencePolicy> {
166 std::collections::HashMap::new()
167 }
168
169 fn service_id(&self) -> &str {
171 std::any::type_name::<Self>()
172 }
173}
174
175#[derive(Clone, Debug)]
179pub struct ResilienceConfig {
180 pub enabled: bool,
182
183 pub default_policies: std::collections::HashMap<String, ResiliencePolicy>,
185
186 pub service_overrides: std::collections::HashMap<String, ResiliencePolicy>,
188}
189
190impl Default for ResilienceConfig {
191 fn default() -> Self {
192 Self {
193 enabled: true,
194 default_policies: std::collections::HashMap::new(),
195 service_overrides: std::collections::HashMap::new(),
196 }
197 }
198}
199
200pub mod policies {
202 use std::time::Duration;
203
204 use super::*;
205
206 pub fn retry(max_attempts: u32) -> ResiliencePolicy {
208 ResiliencePolicy::Retry {
209 max_attempts,
210 backoff: BackoffStrategy::default(),
211 }
212 }
213
214 pub fn circuit_breaker(failure_threshold: u32, recovery_timeout_secs: u64) -> ResiliencePolicy {
216 ResiliencePolicy::CircuitBreaker {
217 failure_threshold,
218 recovery_timeout: Duration::from_secs(recovery_timeout_secs),
219 success_threshold: 3,
220 }
221 }
222
223 pub fn rate_limit(requests_per_second: u32) -> ResiliencePolicy {
225 ResiliencePolicy::RateLimit {
226 requests_per_second,
227 burst_capacity: requests_per_second / 4, }
229 }
230
231 pub fn timeout(seconds: u64) -> ResiliencePolicy {
233 ResiliencePolicy::Timeout {
234 duration: Duration::from_secs(seconds),
235 }
236 }
237
238 pub fn combine(policies: Vec<ResiliencePolicy>) -> ResiliencePolicy {
240 ResiliencePolicy::Combined { policies }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_resilience_policy_creation() {
250 let retry_policy = policies::retry(3);
251 assert_eq!(
252 retry_policy,
253 ResiliencePolicy::Retry {
254 max_attempts: 3,
255 backoff: BackoffStrategy::default(),
256 }
257 );
258
259 let circuit_policy = policies::circuit_breaker(5, 30);
260 match circuit_policy {
261 ResiliencePolicy::CircuitBreaker {
262 failure_threshold,
263 recovery_timeout,
264 ..
265 } => {
266 assert_eq!(failure_threshold, 5);
267 assert_eq!(recovery_timeout, Duration::from_secs(30));
268 }
269 _ => panic!("Expected CircuitBreaker policy"),
270 }
271 }
272
273 #[test]
274 fn test_domain_error_retryable() {
275 assert!(ResilienceDomainError::Timeout {
276 duration: Duration::from_secs(1)
277 }
278 .is_retryable());
279 assert!(ResilienceDomainError::RateLimited { retry_after: None }.is_retryable());
280 assert!(!ResilienceDomainError::CircuitOpen.is_retryable());
281 assert!(!ResilienceDomainError::Cancelled.is_retryable());
282 }
283
284 #[test]
285 fn test_backoff_strategy_default() {
286 let strategy = BackoffStrategy::default();
287 match strategy {
288 BackoffStrategy::Exponential {
289 initial_delay,
290 multiplier,
291 max_delay,
292 jitter,
293 } => {
294 assert_eq!(initial_delay, Duration::from_millis(100));
295 assert_eq!(multiplier, 2.0);
296 assert_eq!(max_delay, Some(Duration::from_secs(30)));
297 assert!(jitter);
298 }
299 _ => panic!("Expected Exponential backoff"),
300 }
301 }
302
303 #[test]
304 fn test_combined_policies() {
305 let retry = policies::retry(3);
306 let timeout = policies::timeout(10);
307 let combined = policies::combine(vec![retry.clone(), timeout]);
308
309 match combined {
310 ResiliencePolicy::Combined { policies } => {
311 assert_eq!(policies.len(), 2);
312 assert_eq!(policies[0], retry);
313 }
314 _ => panic!("Expected Combined policy"),
315 }
316 }
317}