Skip to main content

opensession_api_client/
retry.rs

1use std::time::Duration;
2
3use anyhow::{Context, Result};
4use tracing::warn;
5
6/// Configuration for retry behaviour on upload-style POST requests.
7pub struct RetryConfig {
8    pub max_retries: usize,
9    pub delays: Vec<u64>,
10}
11
12impl Default for RetryConfig {
13    fn default() -> Self {
14        Self {
15            max_retries: 3,
16            delays: vec![1, 2, 4],
17        }
18    }
19}
20
21/// Retry an HTTP POST with exponential backoff.
22///
23/// Retries on network errors and 5xx responses.
24/// Returns immediately on success or 4xx.
25pub async fn retry_post(
26    client: &reqwest::Client,
27    url: &str,
28    auth_token: Option<&str>,
29    body: &serde_json::Value,
30    config: &RetryConfig,
31) -> Result<reqwest::Response> {
32    let max_attempts = config.max_retries + 1;
33
34    for attempt in 0..max_attempts {
35        let mut req = client.post(url).header("Content-Type", "application/json");
36        if let Some(token) = auth_token {
37            req = req.bearer_auth(token);
38        }
39
40        match req.json(body).send().await {
41            Ok(resp) if resp.status().is_server_error() => {
42                if attempt < config.delays.len() {
43                    let status = resp.status();
44                    warn!(
45                        "POST attempt {}/{} failed (HTTP {}), retrying in {}s…",
46                        attempt + 1,
47                        max_attempts,
48                        status,
49                        config.delays[attempt],
50                    );
51                    tokio::time::sleep(Duration::from_secs(config.delays[attempt])).await;
52                } else {
53                    return Ok(resp);
54                }
55            }
56            Ok(resp) => return Ok(resp),
57            Err(e) => {
58                if attempt < config.delays.len() {
59                    warn!(
60                        "POST attempt {}/{} failed ({}), retrying in {}s…",
61                        attempt + 1,
62                        max_attempts,
63                        e,
64                        config.delays[attempt],
65                    );
66                    tokio::time::sleep(Duration::from_secs(config.delays[attempt])).await;
67                } else {
68                    return Err(e).context("Failed to connect after retries");
69                }
70            }
71        }
72    }
73
74    unreachable!()
75}