1use rand::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);
31 }
32 Err(e) => {
33 eprintln!(
34 "[fetch_with_retry] Error on attempt {}/{}: {}",
35 i + 1,
36 retry_attempts,
37 e
38 );
39 let sleep_duration = calc_exponential_backoff_duration(i);
40 sleep(sleep_duration).await;
41 }
42 }
43 }
44
45 Err(format!(
46 "Failed to fetch data from {} after {} attempts.",
47 url, retry_attempts
48 )
49 .into())
50}
51
52fn calc_exponential_backoff_duration(retry_count: u32) -> Duration {
54 let mut rng = rand::rng();
55 let random_part: f64 = rng.random();
57
58 let base = 2u64.pow(retry_count);
59 let backoff_seconds = (base as f64) + random_part;
60 Duration::from_secs_f64(backoff_seconds)
61}