use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub initial_delay: Duration,
pub max_delay: Duration,
pub multiplier: f64,
}
impl RetryPolicy {
pub const DEFAULT_MAX_ATTEMPTS: u32 = 3;
pub const DEFAULT_INITIAL_DELAY: Duration = Duration::from_secs(5);
pub const DEFAULT_MAX_DELAY: Duration = Duration::from_secs(60);
pub const DEFAULT_MULTIPLIER: f64 = 2.0;
#[must_use]
pub const fn new() -> Self {
Self {
max_attempts: Self::DEFAULT_MAX_ATTEMPTS,
initial_delay: Self::DEFAULT_INITIAL_DELAY,
max_delay: Self::DEFAULT_MAX_DELAY,
multiplier: Self::DEFAULT_MULTIPLIER,
}
}
pub const MIN_MAX_ATTEMPTS: u32 = 1;
#[must_use]
pub const fn with_max_attempts(mut self, max_attempts: u32) -> Self {
assert!(
max_attempts >= Self::MIN_MAX_ATTEMPTS,
"max_attempts must be at least 1"
);
self.max_attempts = max_attempts;
self
}
#[must_use]
pub const fn with_initial_delay(mut self, delay: Duration) -> Self {
self.initial_delay = delay;
self
}
#[must_use]
pub const fn with_max_delay(mut self, delay: Duration) -> Self {
self.max_delay = delay;
self
}
#[must_use]
pub fn with_multiplier(mut self, multiplier: f64) -> Self {
assert!(multiplier > 0.0, "multiplier must be positive");
self.multiplier = multiplier;
self
}
#[must_use]
pub fn delay_for_retry(&self, retry: u32) -> Duration {
#[allow(clippy::cast_possible_wrap)]
let multiplier = self.multiplier.powi(retry as i32);
let delay_secs = self.initial_delay.as_secs_f64() * multiplier;
let capped = delay_secs.min(self.max_delay.as_secs_f64());
Duration::from_secs_f64(capped)
}
#[must_use]
pub const fn should_retry(&self, attempt: u32) -> bool {
attempt < self.max_attempts
}
}
impl Default for RetryPolicy {
fn default() -> Self {
Self::new()
}
}