use std::{error::Error, time::Duration};
use qcs_dependencies_client::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: &qcs_dependencies_client::http::HeaderMap,
backoff: &mut ExponentialBackoff,
) -> Option<Duration> {
use time::{OffsetDateTime, format_description::well_known::Rfc2822};
if status_code_is_retry(status) {
if let Some(value) = headers.get(qcs_dependencies_client::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: &qcs_dependencies_client::http::Method) -> bool {
method.is_safe()
}
#[must_use]
pub fn duration_from_reqwest_error(
method: &qcs_dependencies_client::http::Method,
error: &qcs_dependencies_client::reqwest::Error,
backoff: &mut ExponentialBackoff,
) -> Option<Duration> {
if can_retry_method(method) {
if error.is_timeout()
|| error.is_connect()
|| error.is_request()
|| error
.source()
.and_then(|inner| inner.downcast_ref::<hyper::Error>())
.map(|hyper_error| hyper_error.is_closed())
.unwrap_or_default()
{
backoff.next_backoff()
} else {
None
}
} else {
None
}
}
#[must_use]
pub fn duration_from_io_error(
method: &qcs_dependencies_client::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
}
}