1use std::future::Future;
2use std::ops::Range;
3use std::time::Duration;
4
5use crate::error::{ApiError, ApiResult};
6
7#[derive(Debug, Clone)]
9pub struct RetryOptions {
10 pub max_retries: u32,
12 pub base_delay_ms: u64,
14 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
28pub 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
57fn rand_u64() -> u64 {
59 use std::collections::hash_map::RandomState;
60 use std::hash::{BuildHasher, Hasher};
61 RandomState::new().build_hasher().finish()
62}