aether_core/core/
retry_config.rs1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy)]
12pub struct RetryConfig {
13 pub max_attempts: u32,
14 pub base_delay: Duration,
15 pub max_delay: Duration,
16}
17
18impl Default for RetryConfig {
19 fn default() -> Self {
20 Self { max_attempts: 5, base_delay: Duration::from_millis(200), max_delay: Duration::from_secs(30) }
21 }
22}
23
24impl RetryConfig {
25 pub fn disabled() -> Self {
26 Self { max_attempts: 0, ..Self::default() }
27 }
28
29 pub(crate) fn compute_delay(&self, attempt: u32) -> Duration {
32 let multiplier = 2u32.saturating_pow(attempt.saturating_sub(1));
33 self.base_delay.saturating_mul(multiplier).min(self.max_delay)
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn exponential_backoff_doubles_per_attempt() {
43 let config =
44 RetryConfig { max_attempts: 5, base_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30) };
45 assert_eq!(config.compute_delay(1), Duration::from_millis(100));
46 assert_eq!(config.compute_delay(2), Duration::from_millis(200));
47 assert_eq!(config.compute_delay(3), Duration::from_millis(400));
48 assert_eq!(config.compute_delay(4), Duration::from_millis(800));
49 }
50
51 #[test]
52 fn backoff_is_capped_by_max_delay() {
53 let config = RetryConfig {
54 max_attempts: 10,
55 base_delay: Duration::from_millis(100),
56 max_delay: Duration::from_millis(500),
57 };
58 assert_eq!(config.compute_delay(10), Duration::from_millis(500));
59 }
60
61 #[test]
62 fn extreme_attempt_count_does_not_panic() {
63 let config = RetryConfig {
64 max_attempts: 100,
65 base_delay: Duration::from_millis(100),
66 max_delay: Duration::from_secs(30),
67 };
68 assert_eq!(config.compute_delay(99), Duration::from_secs(30));
69 }
70
71 #[test]
72 fn disabled_config_has_zero_max_attempts() {
73 assert_eq!(RetryConfig::disabled().max_attempts, 0);
74 }
75}