use std::time::Duration;
use crate::KitError;
use crate::error::ValidationViolation;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetryPolicy {
max_retries: u32,
initial_backoff: Duration,
max_backoff: Duration,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 3,
initial_backoff: Duration::from_millis(250),
max_backoff: Duration::from_secs(4),
}
}
}
impl RetryPolicy {
pub fn new(
max_retries: u32,
initial_backoff: Duration,
max_backoff: Duration,
) -> Result<Self, KitError> {
if max_retries > 0 && initial_backoff.is_zero() {
return Err(KitError::config(
ValidationViolation::OutOfRange {
field: "retry.initial_backoff",
rule: "positive when retries are enabled",
},
"retry initial backoff must be non-zero when retries are enabled",
));
}
if max_backoff < initial_backoff {
return Err(KitError::config(
ValidationViolation::InvalidValue {
field: "retry.max_backoff",
rule: "at least initial_backoff",
},
"retry maximum backoff must not be shorter than the initial backoff",
));
}
Ok(Self {
max_retries,
initial_backoff,
max_backoff,
})
}
pub(crate) fn delay_for_attempt(&self, attempt: u32) -> Option<Duration> {
if attempt >= self.max_retries {
return None;
}
let multiplier = 2_u32.saturating_pow(attempt);
Some(
self.initial_backoff
.saturating_mul(multiplier)
.min(self.max_backoff),
)
}
}