use std::time::Duration;
#[derive(Debug, Clone, Copy)]
pub struct RetryConfig {
pub max_attempts: u32,
pub base_delay: Duration,
pub max_delay: Duration,
}
impl Default for RetryConfig {
fn default() -> Self {
#[cfg(feature = "retry")]
{
Self {
max_attempts: 3,
base_delay: Duration::from_millis(200),
max_delay: Duration::from_secs(5),
}
}
#[cfg(not(feature = "retry"))]
{
Self {
max_attempts: 1,
base_delay: Duration::from_millis(0),
max_delay: Duration::from_millis(0),
}
}
}
}
impl RetryConfig {
pub fn disabled() -> Self {
Self {
max_attempts: 1,
base_delay: Duration::from_millis(0),
max_delay: Duration::from_millis(0),
}
}
pub(crate) fn backoff_for(&self, attempt: u32) -> Duration {
if attempt == 0 {
return Duration::from_millis(0);
}
let exp = 2u64.saturating_pow(attempt.saturating_sub(1));
let delay = self
.base_delay
.checked_mul(exp.min(u32::MAX as u64) as u32)
.unwrap_or(self.max_delay);
let jitter_ms = (attempt as u64 * 37) % 50;
let jittered = delay.saturating_add(Duration::from_millis(jitter_ms));
jittered.min(self.max_delay)
}
}
pub(crate) fn is_idempotent_method(method: &reqwest::Method) -> bool {
matches!(
*method,
reqwest::Method::GET
| reqwest::Method::HEAD
| reqwest::Method::PUT
| reqwest::Method::DELETE
| reqwest::Method::OPTIONS
)
}