1use rand::{Rng, rng};
2use reqwest::Client;
3use std::time::Duration;
4use tokio::time::sleep;
5
6async fn fetch_once(
10 client: &Client,
11 url: &str,
12) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
13 let resp = client.get(url).send().await?;
14 let text = resp.text().await?;
15 Ok(text)
16}
17
18pub async fn fetch_with_retry(
22 client: &Client,
23 url: &str,
24) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
25 let retry_attempts = 10;
26
27 for i in 0..retry_attempts {
28 match fetch_once(client, url).await {
29 Ok(text) => {
30 return Ok(text);
32 }
33 Err(e) => {
34 eprintln!(
35 "[fetch_with_retry] Error on attempt {}/{}: {}",
36 i + 1,
37 retry_attempts,
38 e
39 );
40 let sleep_duration = calc_exponential_backoff_duration(i);
42 sleep(sleep_duration).await;
43 }
44 }
45 }
46
47 Err(format!(
48 "Failed to fetch data from {} after {} attempts.",
49 url, retry_attempts
50 )
51 .into())
52}
53
54fn calc_exponential_backoff_duration(retry_count: u32) -> Duration {
56 let mut rng = rng();
58 let random_part: f64 = rng.random();
59
60 let base = 2u64.pow(retry_count);
61 let backoff_seconds = (base as f64) + random_part;
62 Duration::from_secs_f64(backoff_seconds)
63}