Skip to main content

reqwest_rest/
retry_strategy.rs

1use reqwest::StatusCode;
2use reqwest_retry::{Retryable, RetryableStrategy, default_on_request_failure};
3
4/// reqwest_retry helpfully logs something when a request is being retried:
5///
6/// https://docs.rs/reqwest-retry/0.5.0/src/reqwest_retry/middleware.rs.html#142
7///
8/// This includes how many times it's been retried and how long it will wait.
9///
10/// But it doesn't log *why* it was retried unfortunately.
11///
12/// I think the easiest way to make it log that is to wrap the RetryStrategy,
13/// which is the component that decides when an error is retriable. Whenever
14/// it says that the error is a Transient error (and should be retried), we can
15/// log the error details.
16/// The wrapped RetryStrategy can then be dropped in to the RetryTransientMiddleware.
17///
18/// Note: We deviated from DefaultRetryableStrategy because we don't consider 429 retryable.
19#[derive(Default)]
20pub struct LoggingRetryableStrategy {}
21
22impl RetryableStrategy for LoggingRetryableStrategy {
23    fn handle(
24        &self,
25        res: &Result<reqwest::Response, reqwest_middleware::Error>,
26    ) -> Option<Retryable> {
27        let result = match res {
28            Ok(resp) => on_request_success(resp),
29            Err(err) => default_on_request_failure(err),
30        };
31
32        if let Some(Retryable::Transient) = &result {
33            match res {
34                Ok(resp) => {
35                    #[cfg(feature = "tracing")]
36                    tracing::warn!(
37                        "Retrying a transient error: response status code = {}",
38                        resp.status()
39                    )
40                }
41                Err(err) => {
42                    #[cfg(feature = "tracing")]
43                    tracing::warn!("Retrying a transient error: {err}")
44                }
45            }
46        }
47
48        result
49    }
50}
51
52// Variation on upstream reqwest-middleware default_on_request_success function
53// What we changed is, 429 TOO MANY REQUESTS should not be retried automatically, because it means we are being rate limited.
54// Higher-level logic should decide if we should back off now for several minutes and try again, or forget it
55// and do something else.
56//
57// https://github.com/TrueLayer/reqwest-middleware/blob/0b7624097964ce9752bd9fbf06618848edd3150e/reqwest-retry/src/retryable_strategy.rs#L113
58
59fn on_request_success(success: &reqwest::Response) -> Option<Retryable> {
60    let status = success.status();
61    if status.is_success() {
62        None
63    } else if status.is_server_error() {
64        Some(Retryable::Transient)
65    } else if status == StatusCode::REQUEST_TIMEOUT {
66        // 400-level error but maybe retriable
67        Some(Retryable::Transient)
68    } else {
69        Some(Retryable::Fatal)
70    }
71}