use crate::error::AntibotError;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub initial_delay: Duration,
pub max_delay: Duration,
pub multiplier: f64,
pub jitter: bool,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_attempts: 3,
initial_delay: Duration::from_millis(500),
max_delay: Duration::from_secs(10),
multiplier: 2.0,
jitter: true,
}
}
}
impl RetryPolicy {
pub fn no_retries() -> Self {
Self {
max_attempts: 1,
..Self::default()
}
}
pub fn backoff_for_attempt(&self, attempt: u32) -> Duration {
if attempt <= 1 {
return Duration::ZERO;
}
let exp = (attempt - 1) as i32 - 1;
let scale = self.multiplier.powi(exp.max(0));
let nanos = self.initial_delay.as_nanos() as f64 * scale;
let mut delay = Duration::from_nanos(nanos.min(u64::MAX as f64) as u64);
if delay > self.max_delay {
delay = self.max_delay;
}
if self.jitter {
delay = apply_jitter(delay);
}
delay
}
pub fn is_retryable(&self, err: &AntibotError) -> bool {
matches!(
err,
AntibotError::Http(_)
| AntibotError::UnexpectedResponse(_)
| AntibotError::ChallengeFailed { .. }
)
}
}
fn apply_jitter(d: Duration) -> Duration {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|t| t.subsec_nanos())
.unwrap_or(0) as u64;
let frac = (seed % 1000) as f64 / 1000.0; let factor = 0.75 + 0.5 * frac; let nanos = (d.as_nanos() as f64 * factor) as u64;
Duration::from_nanos(nanos)
}