use std::time::Duration;
use reqwest::header::RETRY_AFTER;
use reqwest::{Error as ReqwestError, Response, StatusCode};
use crate::dataset::{FetchError, io::retry::Transient};
const TRANSIENT_STATUS: [u16; 6] = [408, 429, 500, 502, 503, 504];
pub(super) enum Attempt {
Status {
status: StatusCode,
retry_after: Option<Duration>,
},
TooLarge(Option<u64>),
Request(ReqwestError),
}
impl Attempt {
pub(super) fn status(response: &Response) -> Self {
Self::Status {
status: response.status(),
retry_after: retry_after(response),
}
}
pub(super) fn into_error(self, location: String, attempts: u32, limit: u64) -> FetchError {
match self {
Self::Status { status, .. } => FetchError::Status {
url: location,
status: status.as_u16(),
attempts,
},
Self::TooLarge(size) => FetchError::TooLarge {
location,
limit,
size,
},
Self::Request(error) => FetchError::Io {
location,
attempts,
timeout: error.is_timeout(),
source: error.without_url().into(),
},
}
}
}
impl Transient for Attempt {
fn is_transient(&self) -> bool {
match self {
Self::Status { status, .. } => TRANSIENT_STATUS.contains(&status.as_u16()),
Self::TooLarge(_) => false,
Self::Request(error) => {
error.is_timeout() || error.is_connect() || error.is_request() || error.is_body()
}
}
}
fn retry_after(&self) -> Option<Duration> {
match self {
Self::Status { retry_after, .. } => *retry_after,
_ => None,
}
}
}
fn retry_after(response: &Response) -> Option<Duration> {
let value = response.headers().get(RETRY_AFTER)?.to_str().ok()?;
value.trim().parse::<u64>().ok().map(Duration::from_secs)
}