qcs_api_client_common/
backoff.rs1use std::{error::Error as _, time::Duration};
10
11use qcs_dependencies_client::http::StatusCode;
12
13pub use ::backon::*;
14
15#[allow(clippy::module_name_repetitions)]
20#[must_use]
21pub const fn default_backoff() -> ExponentialBuilder {
22 ExponentialBuilder::new()
23 .with_jitter()
24 .with_min_delay(Duration::from_millis(500))
25 .with_factor(1.5)
26 .with_max_delay(Duration::from_secs(30))
27 .with_total_delay(Some(Duration::from_secs(300)))
28 .without_max_times()
29}
30
31#[must_use]
33pub const fn status_code_is_retry(code: StatusCode) -> bool {
34 matches!(
35 code,
36 StatusCode::SERVICE_UNAVAILABLE | StatusCode::BAD_GATEWAY | StatusCode::TOO_MANY_REQUESTS
37 )
38}
39
40#[must_use]
43pub fn duration_from_response(
44 status: StatusCode,
45 headers: &qcs_dependencies_client::http::HeaderMap,
46 backoff: &mut ExponentialBackoff,
47) -> Option<Duration> {
48 use time::{OffsetDateTime, format_description::well_known::Rfc2822};
49
50 if status_code_is_retry(status) {
51 if let Some(value) = headers.get(qcs_dependencies_client::http::header::RETRY_AFTER)
52 && let Ok(value) = value.to_str()
53 {
54 if let Ok(value) = value.parse::<u64>() {
55 return Some(Duration::from_secs(value));
56 } else if let Ok(date) = OffsetDateTime::parse(value, &Rfc2822) {
57 let duration = date - OffsetDateTime::now_utc();
58 let std_duration: Duration = duration.try_into().ok()?;
61 return Some(std_duration);
62 }
63 }
64
65 backoff.next()
66 } else {
67 None
68 }
69}
70
71fn can_retry_method(method: &qcs_dependencies_client::http::Method) -> bool {
72 method.is_safe()
80}
81
82#[must_use]
85pub fn duration_from_reqwest_error(
86 method: &qcs_dependencies_client::http::Method,
87 error: &qcs_dependencies_client::reqwest::Error,
88 backoff: &mut ExponentialBackoff,
89) -> Option<Duration> {
90 if can_retry_method(method) {
91 if error.is_timeout()
92 || error.is_connect()
93 || error.is_request()
94 || error
95 .source()
96 .and_then(|inner| inner.downcast_ref::<hyper::Error>())
97 .is_some_and(hyper::Error::is_closed)
98 {
99 backoff.next()
100 } else {
101 None
102 }
103 } else {
104 None
105 }
106}
107
108#[must_use]
111pub fn duration_from_io_error(
112 method: &qcs_dependencies_client::http::Method,
113 error: &std::io::Error,
114 backoff: &mut ExponentialBackoff,
115) -> Option<Duration> {
116 use std::io::ErrorKind;
117 if can_retry_method(method) {
118 if matches!(
119 error.kind(),
120 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
121 ) {
122 backoff.next()
123 } else {
124 None
125 }
126 } else {
127 None
128 }
129}