1use std::time::Duration;
4
5use reqwest::StatusCode;
6
7#[derive(Debug, Clone)]
9pub struct RetryPolicy {
10 pub max_attempts: u32,
12 pub base_delay: Duration,
14 pub max_delay: Duration,
16}
17
18impl Default for RetryPolicy {
19 fn default() -> Self {
20 Self {
21 max_attempts: 3,
22 base_delay: Duration::from_secs(1),
23 max_delay: Duration::from_secs(30),
24 }
25 }
26}
27
28#[derive(Debug, Clone, Copy)]
30pub(crate) enum RetryDecision {
31 Retry(Duration),
33 Stop,
35}
36
37impl RetryPolicy {
38 pub(crate) fn decide_status(
40 &self,
41 attempt: u32,
42 status: StatusCode,
43 retry_after: Option<Duration>,
44 ) -> RetryDecision {
45 if attempt >= self.max_attempts {
46 return RetryDecision::Stop;
47 }
48 match status.as_u16() {
49 429 => RetryDecision::Retry(retry_after.unwrap_or_else(|| self.backoff(attempt))),
50 500..=599 => RetryDecision::Retry(self.backoff(attempt)),
51 _ => RetryDecision::Stop,
52 }
53 }
54
55 pub(crate) fn decide_transport(&self, attempt: u32, err: &reqwest::Error) -> RetryDecision {
63 if attempt >= self.max_attempts {
64 return RetryDecision::Stop;
65 }
66 if err.is_connect() || err.is_timeout() {
70 RetryDecision::Retry(self.backoff(attempt))
71 } else {
72 RetryDecision::Stop
73 }
74 }
75
76 fn backoff(&self, attempt: u32) -> Duration {
78 let exp = 2u64.saturating_pow(attempt);
79 let factor = u32::try_from(exp).unwrap_or(u32::MAX);
80 let max = (self.base_delay * factor).min(self.max_delay);
81 let nanos = u64::try_from(max.as_nanos()).unwrap_or(u64::MAX);
83 let jitter = rand_nanos(nanos);
84 Duration::from_nanos(jitter)
85 }
86}
87
88fn rand_nanos(max: u64) -> u64 {
90 use std::time::SystemTime;
91 if max == 0 {
92 return 0;
93 }
94 let n = SystemTime::now()
95 .duration_since(SystemTime::UNIX_EPOCH)
96 .map_or(0, |d| u64::try_from(d.as_nanos()).unwrap_or(u64::MAX));
97 n % max
98}
99
100pub(crate) fn parse_retry_after(v: &reqwest::header::HeaderValue) -> Option<Duration> {
102 let s = v.to_str().ok()?;
103 s.trim().parse::<u64>().ok().map(Duration::from_secs)
104}