use super::*;
pub(crate) const TRANSIENT_RETRY_ATTEMPTS: u32 = 3;
pub(crate) const TRANSIENT_RETRY_BASE_MS: u64 = 250;
pub(crate) const TRANSIENT_RETRY_MAX_MS: u64 = 4_000;
#[derive(Debug)]
pub(crate) enum RetryError {
Transient(anyhow::Error),
Terminal(anyhow::Error),
}
pub(crate) fn transient(e: reqwest::Error) -> RetryError {
RetryError::Transient(anyhow!("reqwest: {e}"))
}
pub(crate) async fn retry_transient<F, Fut, T>(label: &'static str, mut op: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = std::result::Result<T, RetryError>>,
{
let mut backoff_ms = TRANSIENT_RETRY_BASE_MS;
let mut last_err: Option<anyhow::Error> = None;
for attempt in 0..TRANSIENT_RETRY_ATTEMPTS {
match op().await {
Ok(v) => return Ok(v),
Err(RetryError::Terminal(e)) => return Err(e),
Err(RetryError::Transient(e)) => {
last_err = Some(e);
if attempt + 1 < TRANSIENT_RETRY_ATTEMPTS {
tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
backoff_ms = (backoff_ms * 2).min(TRANSIENT_RETRY_MAX_MS);
}
}
}
}
Err(last_err.unwrap_or_else(|| anyhow!("{label} exhausted {TRANSIENT_RETRY_ATTEMPTS} retries")))
}
pub(crate) fn is_terminal_error(code: &str) -> bool {
matches!(
code,
"ERROR_KEY_DOES_NOT_EXIST"
| "ERROR_WRONG_USER_KEY"
| "ERROR_ZERO_BALANCE"
| "ERROR_NO_SLOT_AVAILABLE"
| "ERROR_IP_NOT_ALLOWED"
| "IP_BANNED"
| "ERROR_BAD_TOKEN_OR_PAGEURL"
| "ERROR_BAD_DUPLICATES"
| "ERROR_PAGEURL"
| "ERROR_GOOGLEKEY"
| "ERROR_TOKEN_EXPIRED"
| "ERROR_CAPTCHA_UNSOLVABLE"
| "ERROR_BAD_PARAMETERS"
| "ERROR_WRONG_GOOGLEKEY"
| "ERROR_DOMAIN_NOT_ALLOWED"
| "ERROR_PROXY_CONNECT_REFUSED"
| "ERROR_PROXY_FORMAT"
)
}