use std::time::Duration;
use http::StatusCode;
use ::backoff::backoff::Backoff;
pub use ::backoff::*;
#[allow(clippy::module_name_repetitions)]
#[must_use]
pub fn default_backoff() -> ExponentialBackoff {
ExponentialBackoffBuilder::new()
.with_max_elapsed_time(Some(Duration::from_secs(300)))
.with_max_interval(Duration::from_secs(30))
.build()
}
#[must_use]
pub const fn status_code_is_retry(code: StatusCode) -> bool {
matches!(
code,
StatusCode::SERVICE_UNAVAILABLE | StatusCode::BAD_GATEWAY | StatusCode::TOO_MANY_REQUESTS
)
}
#[must_use]
pub fn duration_from_response(
status: StatusCode,
headers: &http::HeaderMap,
backoff: &mut ExponentialBackoff,
) -> Option<Duration> {
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
if status_code_is_retry(status) {
if let Some(value) = headers.get(http::header::RETRY_AFTER) {
if let Ok(value) = value.to_str() {
if let Ok(value) = value.parse::<u64>() {
return Some(Duration::from_secs(value));
} else if let Ok(date) = OffsetDateTime::parse(value, &Rfc2822) {
let duration = date - OffsetDateTime::now_utc();
let std_duration: Duration = duration.try_into().ok()?;
return Some(std_duration);
}
}
}
backoff.next_backoff()
} else {
None
}
}
fn can_retry_method(method: &http::Method) -> bool {
method.is_safe()
}
#[must_use]
pub fn duration_from_reqwest_error(
method: &http::Method,
error: &reqwest::Error,
backoff: &mut ExponentialBackoff,
) -> Option<Duration> {
if can_retry_method(method) {
if error.is_timeout()
|| error.is_connect()
|| error.is_request()
|| format!("{error:?}").contains("source: hyper::Error(ChannelClosed)")
{
backoff.next_backoff()
} else {
None
}
} else {
None
}
}
#[must_use]
pub fn duration_from_io_error(
method: &http::Method,
error: &std::io::Error,
backoff: &mut ExponentialBackoff,
) -> Option<Duration> {
use std::io::ErrorKind;
if can_retry_method(method) {
if matches!(
error.kind(),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
) {
backoff.next_backoff()
} else {
None
}
} else {
None
}
}