1use std::time::Duration;
6
7#[derive(Debug, Clone)]
9pub struct RetryPolicy {
10 pub max_retries: u32,
12 pub backoff: BackoffStrategy,
14}
15
16#[derive(Debug, Clone)]
18pub enum BackoffStrategy {
19 Fixed(Duration),
21 Exponential {
23 initial: Duration,
24 multiplier: f64,
25 max: Duration,
26 },
27}
28
29impl RetryPolicy {
30 pub fn fixed(max_retries: u32, delay: Duration) -> Self {
32 Self {
33 max_retries,
34 backoff: BackoffStrategy::Fixed(delay),
35 }
36 }
37
38 pub fn exponential(max_retries: u32, initial: Duration, multiplier: f64, max: Duration) -> Self {
46 Self {
47 max_retries,
48 backoff: BackoffStrategy::Exponential {
49 initial,
50 multiplier,
51 max,
52 },
53 }
54 }
55
56 pub fn exponential_default(max_retries: u32, initial_ms: u64) -> Self {
59 Self::exponential(
60 max_retries,
61 Duration::from_millis(initial_ms),
62 2.0,
63 Duration::from_secs(30),
64 )
65 }
66
67 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
69 match &self.backoff {
70 BackoffStrategy::Fixed(d) => *d,
71 BackoffStrategy::Exponential {
72 initial,
73 multiplier,
74 max,
75 } => {
76 let delay_ms =
77 initial.as_millis() as f64 * multiplier.powi(attempt as i32);
78 let delay = Duration::from_millis(delay_ms as u64);
79 if delay > *max { *max } else { delay }
80 }
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn fixed_delay_is_constant() {
91 let policy = RetryPolicy::fixed(3, Duration::from_millis(100));
92 assert_eq!(policy.delay_for_attempt(0), Duration::from_millis(100));
93 assert_eq!(policy.delay_for_attempt(1), Duration::from_millis(100));
94 assert_eq!(policy.delay_for_attempt(2), Duration::from_millis(100));
95 }
96
97 #[test]
98 fn exponential_delay_doubles() {
99 let policy = RetryPolicy::exponential(
100 5,
101 Duration::from_millis(100),
102 2.0,
103 Duration::from_secs(10),
104 );
105 assert_eq!(policy.delay_for_attempt(0), Duration::from_millis(100));
106 assert_eq!(policy.delay_for_attempt(1), Duration::from_millis(200));
107 assert_eq!(policy.delay_for_attempt(2), Duration::from_millis(400));
108 assert_eq!(policy.delay_for_attempt(3), Duration::from_millis(800));
109 }
110
111 #[test]
112 fn exponential_delay_caps_at_max() {
113 let policy = RetryPolicy::exponential(
114 10,
115 Duration::from_millis(100),
116 2.0,
117 Duration::from_millis(500),
118 );
119 assert_eq!(policy.delay_for_attempt(0), Duration::from_millis(100));
120 assert_eq!(policy.delay_for_attempt(1), Duration::from_millis(200));
121 assert_eq!(policy.delay_for_attempt(2), Duration::from_millis(400));
122 assert_eq!(policy.delay_for_attempt(3), Duration::from_millis(500)); assert_eq!(policy.delay_for_attempt(4), Duration::from_millis(500)); }
125
126 #[test]
127 fn default_exponential_starts_correctly() {
128 let policy = RetryPolicy::exponential_default(3, 100);
129 assert_eq!(policy.max_retries, 3);
130 assert_eq!(policy.delay_for_attempt(0), Duration::from_millis(100));
131 assert_eq!(policy.delay_for_attempt(1), Duration::from_millis(200));
132 }
133}