use std::time::Duration;
#[derive(Debug, Clone)]
pub struct BackoffConfig {
pub base_delay: Duration,
pub max_delay: Duration,
pub jitter: bool,
pub max_http_retries: u32,
}
impl Default for BackoffConfig {
fn default() -> Self {
Self {
base_delay: Duration::from_millis(500),
max_delay: Duration::from_secs(30),
jitter: true,
max_http_retries: 3,
}
}
}
impl BackoffConfig {
pub fn delay_for(&self, attempt: u32) -> Duration {
let exp = self.base_delay.saturating_mul(1u32 << attempt.min(16));
let capped = exp.min(self.max_delay);
if self.jitter {
let factor = 0.5 + rand::random::<f64>() * 0.5;
capped.mul_f64(factor)
} else {
capped
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let config = BackoffConfig::default();
assert_eq!(config.base_delay, Duration::from_millis(500));
assert_eq!(config.max_delay, Duration::from_secs(30));
assert!(config.jitter);
assert_eq!(config.max_http_retries, 3);
}
#[test]
fn delay_exponential() {
let config = BackoffConfig {
jitter: false,
..Default::default()
};
assert_eq!(config.delay_for(0), Duration::from_millis(500));
assert_eq!(config.delay_for(1), Duration::from_millis(1000));
assert_eq!(config.delay_for(2), Duration::from_millis(2000));
assert_eq!(config.delay_for(3), Duration::from_millis(4000));
}
#[test]
fn delay_capped() {
let config = BackoffConfig {
jitter: false,
..Default::default()
};
let delay = config.delay_for(10);
assert!(delay <= Duration::from_secs(30));
assert_eq!(delay, Duration::from_secs(30));
}
#[test]
fn delay_with_jitter() {
let config = BackoffConfig::default();
for _ in 0..20 {
let delay = config.delay_for(0);
assert!(delay >= Duration::from_millis(250));
assert!(delay < Duration::from_millis(500));
}
}
#[test]
fn delay_no_jitter() {
let config = BackoffConfig {
jitter: false,
..Default::default()
};
assert_eq!(config.delay_for(0), Duration::from_millis(500));
assert_eq!(config.delay_for(0), Duration::from_millis(500));
}
#[test]
fn clone_and_debug() {
let config = BackoffConfig::default();
let cloned = config.clone();
assert_eq!(cloned.base_delay, config.base_delay);
let debug = format!("{config:?}");
assert!(debug.contains("BackoffConfig"));
}
}