Skip to main content

usesend_api/
retry.rs

1use std::future::Future;
2use std::ops::Range;
3use std::time::Duration;
4
5use crate::error::{ApiError, ApiResult};
6
7/// Options for retrying rate-limited requests.
8#[derive(Debug, Clone)]
9pub struct RetryOptions {
10    /// Maximum number of retry attempts (default: 3).
11    pub max_retries: u32,
12    /// Base delay in milliseconds between retries (default: 1000).
13    pub base_delay_ms: u64,
14    /// Random jitter range in milliseconds added to delay (default: 0..30).
15    pub jitter_range_ms: Range<u64>,
16}
17
18impl Default for RetryOptions {
19    fn default() -> Self {
20        Self {
21            max_retries: 3,
22            base_delay_ms: 1000,
23            jitter_range_ms: 0..30,
24        }
25    }
26}
27
28/// Retry a future-producing closure on `ApiError::RateLimit`.
29///
30/// If the API returns a `ratelimit-reset` header, that value (in seconds) is
31/// used as the sleep duration. Otherwise, `opts.base_delay_ms` is used.
32pub async fn send_with_retry<F, Fut, T>(f: F, opts: &RetryOptions) -> ApiResult<T>
33where
34    F: Fn() -> Fut,
35    Fut: Future<Output = ApiResult<T>>,
36{
37    let mut retries_left = opts.max_retries;
38    loop {
39        match f().await {
40            Ok(v) => return Ok(v),
41            Err(ApiError::RateLimit { retry_after }) if retries_left > 0 => {
42                retries_left -= 1;
43                let base = retry_after.map(|s| s * 1000).unwrap_or(opts.base_delay_ms);
44                let jitter = if opts.jitter_range_ms.is_empty() {
45                    0
46                } else {
47                    opts.jitter_range_ms.start
48                        + (rand_u64() % (opts.jitter_range_ms.end - opts.jitter_range_ms.start))
49                };
50                tokio::time::sleep(Duration::from_millis(base + jitter)).await;
51            }
52            Err(e) => return Err(e),
53        }
54    }
55}
56
57/// Simple non-cryptographic random u64 using std.
58fn rand_u64() -> u64 {
59    use std::collections::hash_map::RandomState;
60    use std::hash::{BuildHasher, Hasher};
61    RandomState::new().build_hasher().finish()
62}