use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ReconnectConfig {
pub max_retries: u32,
pub retry_interval: Duration,
pub backoff_multiplier: f32,
}
impl Default for ReconnectConfig {
fn default() -> Self {
Self {
max_retries: 3,
retry_interval: Duration::from_secs(1),
backoff_multiplier: 2.0,
}
}
}
impl ReconnectConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn fixed_interval(max_retries: u32, interval: Duration) -> Self {
Self {
max_retries,
retry_interval: interval,
backoff_multiplier: 1.0,
}
}
#[must_use]
pub fn exponential_backoff(
max_retries: u32,
initial_interval: Duration,
multiplier: f32,
) -> Self {
Self {
max_retries,
retry_interval: initial_interval,
backoff_multiplier: multiplier,
}
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn interval_for_attempt(&self, attempt: u32) -> Duration {
if attempt == 0 {
return self.retry_interval;
}
let multiplier = self.backoff_multiplier.powi(attempt as i32);
Duration::from_secs_f32(self.retry_interval.as_secs_f32() * multiplier)
}
#[must_use]
pub fn should_retry(&self, current_attempt: u32) -> bool {
self.max_retries == 0 || current_attempt < self.max_retries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ReconnectConfig::default();
assert_eq!(config.max_retries, 3);
assert_eq!(config.retry_interval, Duration::from_secs(1));
assert!((config.backoff_multiplier - 2.0).abs() < f32::EPSILON);
}
#[test]
fn test_fixed_interval() {
let config = ReconnectConfig::fixed_interval(5, Duration::from_millis(500));
assert_eq!(config.max_retries, 5);
assert!((config.backoff_multiplier - 1.0).abs() < f32::EPSILON);
assert_eq!(
config.interval_for_attempt(0),
config.interval_for_attempt(3)
);
}
#[test]
fn test_exponential_backoff() {
let config = ReconnectConfig::exponential_backoff(5, Duration::from_secs(1), 2.0);
assert_eq!(config.interval_for_attempt(0), Duration::from_secs(1));
assert_eq!(config.interval_for_attempt(1), Duration::from_secs(2));
assert_eq!(config.interval_for_attempt(2), Duration::from_secs(4));
}
#[test]
fn test_should_retry() {
let config = ReconnectConfig {
max_retries: 3,
..Default::default()
};
assert!(config.should_retry(0));
assert!(config.should_retry(2));
assert!(!config.should_retry(3));
}
#[test]
fn test_unlimited_retries() {
let config = ReconnectConfig {
max_retries: 0, ..Default::default()
};
assert!(config.should_retry(100));
assert!(config.should_retry(1000));
}
}