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}