use std::time::Duration;
use serde_json::Value;
use crate::transport::context::ProviderError;
const MAX_RETRIES: u32 = 3;
const INITIAL_RETRY_BACKOFF_SECS: u64 = 1;
pub async fn send_with_retry<F, Fut>(mut make_request: F) -> Result<Value, ProviderError>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<reqwest::Response, reqwest::Error>>,
{
let mut retries = 0;
loop {
let resp = make_request().await?;
let status = resp.status().as_u16();
if status == 401 || status == 403 {
let resp_body = resp.text().await.unwrap_or_default();
return Err(ProviderError::Http {
status,
body: resp_body,
});
}
if status == 429 {
retries += 1;
if retries > MAX_RETRIES {
return Err(ProviderError::RateLimited { retries });
}
let backoff = INITIAL_RETRY_BACKOFF_SECS * (1 << (retries - 1));
tracing::warn!(
retry = retries,
backoff_secs = backoff,
"rate limited, retrying"
);
tokio::time::sleep(Duration::from_secs(backoff)).await;
continue;
}
if !resp.status().is_success() {
let resp_body = resp.text().await.unwrap_or_default();
return Err(ProviderError::Http {
status,
body: resp_body,
});
}
let resp_body: Value = match resp.json().await {
Ok(v) => v,
Err(e) => {
retries += 1;
if retries > MAX_RETRIES {
return Err(ProviderError::Parse(format!("JSON parse error: {e}")));
}
tracing::warn!(error = %e, "malformed provider JSON, retrying");
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
};
return Ok(resp_body);
}
}