qcs_api_client_common/
backoff.rs1use std::{error::Error, time::Duration};
7
8use qcs_dependencies_client::http::StatusCode;
9
10use ::backoff::backoff::Backoff;
11pub use ::backoff::*;
12
13#[allow(clippy::module_name_repetitions)]
18#[must_use]
19pub fn default_backoff() -> ExponentialBackoff {
20 ExponentialBackoffBuilder::new()
21 .with_max_elapsed_time(Some(Duration::from_secs(300)))
22 .with_max_interval(Duration::from_secs(30))
23 .build()
24}
25
26#[must_use]
28pub const fn status_code_is_retry(code: StatusCode) -> bool {
29 matches!(
30 code,
31 StatusCode::SERVICE_UNAVAILABLE | StatusCode::BAD_GATEWAY | StatusCode::TOO_MANY_REQUESTS
32 )
33}
34
35#[must_use]
38pub fn duration_from_response(
39 status: StatusCode,
40 headers: &qcs_dependencies_client::http::HeaderMap,
41 backoff: &mut ExponentialBackoff,
42) -> Option<Duration> {
43 use time::{OffsetDateTime, format_description::well_known::Rfc2822};
44
45 if status_code_is_retry(status) {
46 if let Some(value) = headers.get(qcs_dependencies_client::http::header::RETRY_AFTER) {
47 if let Ok(value) = value.to_str() {
48 if let Ok(value) = value.parse::<u64>() {
49 return Some(Duration::from_secs(value));
50 } else if let Ok(date) = OffsetDateTime::parse(value, &Rfc2822) {
51 let duration = date - OffsetDateTime::now_utc();
52 let std_duration: Duration = duration.try_into().ok()?;
55 return Some(std_duration);
56 }
57 }
58 }
59
60 backoff.next_backoff()
61 } else {
62 None
63 }
64}
65
66fn can_retry_method(method: &qcs_dependencies_client::http::Method) -> bool {
67 method.is_safe()
75}
76
77#[must_use]
80pub fn duration_from_reqwest_error(
81 method: &qcs_dependencies_client::http::Method,
82 error: &qcs_dependencies_client::reqwest::Error,
83 backoff: &mut ExponentialBackoff,
84) -> Option<Duration> {
85 if can_retry_method(method) {
86 if error.is_timeout()
87 || error.is_connect()
88 || error.is_request()
89 || error
90 .source()
91 .and_then(|inner| inner.downcast_ref::<hyper::Error>())
92 .map(|hyper_error| hyper_error.is_closed())
93 .unwrap_or_default()
94 {
95 backoff.next_backoff()
96 } else {
97 None
98 }
99 } else {
100 None
101 }
102}
103
104#[must_use]
107pub fn duration_from_io_error(
108 method: &qcs_dependencies_client::http::Method,
109 error: &std::io::Error,
110 backoff: &mut ExponentialBackoff,
111) -> Option<Duration> {
112 use std::io::ErrorKind;
113 if can_retry_method(method) {
114 if matches!(
115 error.kind(),
116 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
117 ) {
118 backoff.next_backoff()
119 } else {
120 None
121 }
122 } else {
123 None
124 }
125}