use std::time::Duration;
use rand::RngExt;
#[derive(Debug, Clone, PartialEq)]
pub enum RetryDelay {
None,
Fixed(Duration),
Random {
min: Duration,
max: Duration,
},
Exponential {
initial: Duration,
max: Duration,
multiplier: f64,
},
}
impl RetryDelay {
#[inline]
pub fn none() -> Self {
Self::None
}
#[inline]
pub fn fixed(delay: Duration) -> Self {
Self::Fixed(delay)
}
#[inline]
pub fn random(min: Duration, max: Duration) -> Self {
Self::Random { min, max }
}
#[inline]
pub fn exponential(initial: Duration, max: Duration, multiplier: f64) -> Self {
Self::Exponential {
initial,
max,
multiplier,
}
}
pub fn base_delay(&self, attempt: u32) -> Duration {
match self {
Self::None => Duration::ZERO,
Self::Fixed(delay) => *delay,
Self::Random { min, max } => {
if min >= max {
return *min;
}
let mut rng = rand::rng();
let min_nanos = Self::duration_to_nanos_u64(*min);
let max_nanos = Self::duration_to_nanos_u64(*max);
Duration::from_nanos(rng.random_range(min_nanos..=max_nanos))
}
Self::Exponential {
initial,
max,
multiplier,
} => Self::exponential_delay(*initial, *max, *multiplier, attempt),
}
}
fn duration_to_nanos_u64(duration: Duration) -> u64 {
duration.as_nanos().min(u64::MAX as u128) as u64
}
fn exponential_delay(
initial: Duration,
max: Duration,
multiplier: f64,
attempt: u32,
) -> Duration {
let power = attempt.saturating_sub(1);
let base_nanos = initial.as_nanos() as f64;
let max_nanos = max.as_nanos() as f64;
let nanos = base_nanos * multiplier.powi(power.min(i32::MAX as u32) as i32);
if !nanos.is_finite() || nanos >= max_nanos {
return max;
}
Duration::from_nanos(nanos.max(0.0) as u64)
}
pub fn validate(&self) -> Result<(), String> {
match self {
Self::None => Ok(()),
Self::Fixed(delay) => {
if delay.is_zero() {
Err("fixed delay cannot be zero".to_string())
} else {
Ok(())
}
}
Self::Random { min, max } => {
if min.is_zero() {
Err("random delay minimum cannot be zero".to_string())
} else if min > max {
Err("random delay minimum cannot be greater than maximum".to_string())
} else {
Ok(())
}
}
Self::Exponential {
initial,
max,
multiplier,
} => {
if initial.is_zero() {
Err("exponential delay initial value cannot be zero".to_string())
} else if max < initial {
Err("exponential delay maximum cannot be smaller than initial".to_string())
} else if !multiplier.is_finite() || *multiplier <= 1.0 {
Err(
"exponential delay multiplier must be finite and greater than 1.0"
.to_string(),
)
} else {
Ok(())
}
}
}
}
}
impl Default for RetryDelay {
#[inline]
fn default() -> Self {
Self::Exponential {
initial: Duration::from_secs(1),
max: Duration::from_secs(60),
multiplier: 2.0,
}
}
}