rama_http/layer/retry/policy.rs
1use super::RetryBody;
2use crate::Request;
3
4/// A "retry policy" to classify if a request should be retried.
5///
6/// # Example
7///
8/// ```
9/// use rama_http::Request;
10/// use rama_http::layer::retry::{Policy, PolicyResult, RetryBody};
11/// use std::sync::Arc;
12/// use parking_lot::Mutex;
13///
14/// struct Attempts(Arc<Mutex<usize>>);
15///
16/// impl<R, E> Policy< R, E> for Attempts
17/// where
18/// R: Send + 'static,
19/// E: Send + Sync + 'static,
20/// {
21/// async fn retry(&self, req: Request<RetryBody>, result: Result<R, E>) -> PolicyResult<R, E> {
22/// match result {
23/// Ok(_) => {
24/// // Treat all `Response`s as success,
25/// // so don't retry...
26/// PolicyResult::Abort(result)
27/// },
28/// Err(_) => {
29/// // Treat all errors as failures...
30/// // But we limit the number of attempts...
31/// let mut attempts = self.0.lock();
32/// if *attempts > 0 {
33/// // Try again!
34/// *attempts -= 1;
35/// PolicyResult::Retry { req }
36/// } else {
37/// // Used all our attempts, no retry...
38/// PolicyResult::Abort(result)
39/// }
40/// }
41/// }
42/// }
43///
44/// fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
45/// Some(req.clone())
46/// }
47/// }
48/// ```
49pub trait Policy<R, E>: Send + Sync + 'static {
50 /// Check the policy if a certain request should be retried.
51 ///
52 /// This method is passed a reference to the original request, and either
53 /// the [`Service::Output`] or [`Service::Error`] from the inner service.
54 ///
55 /// If the request should **not** be retried, return `None`.
56 ///
57 /// If the request *should* be retried, return `Some` future that will delay
58 /// the next retry of the request. This can be used to sleep for a certain
59 /// duration, to wait for some external condition to be met before retrying,
60 /// or resolve right away, if the request should be retried immediately.
61 ///
62 /// ## Mutating Requests
63 ///
64 /// The policy MAY chose to mutate the `req`: if the request is mutated, the
65 /// mutated request will be sent to the inner service in the next retry.
66 /// This can be helpful for use cases like tracking the retry count in a
67 /// header.
68 ///
69 /// ## Mutating Results
70 ///
71 /// The policy MAY chose to mutate the result. This enables the retry
72 /// policy to convert a failure into a success and vice versa. For example,
73 /// if the policy is used to poll while waiting for a state change, the
74 /// policy can switch the result to emit a specific error when retries are
75 /// exhausted.
76 ///
77 /// The policy can also record metadata on the request to include
78 /// information about the number of retries required or to record that a
79 /// failure failed after exhausting all retries.
80 ///
81 /// [`Service::Output`]: rama_core::Service::Output
82 /// [`Service::Error`]: rama_core::Service::Error
83 fn retry(
84 &self,
85
86 req: Request<RetryBody>,
87 result: Result<R, E>,
88 ) -> impl Future<Output = PolicyResult<R, E>> + Send + '_;
89
90 /// Tries to clone a request before being passed to the inner service.
91 ///
92 /// If the request cannot be cloned, return [`None`]. Moreover, the retry
93 /// function will not be called if the [`None`] is returned.
94 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>>;
95}
96
97impl<P, R, E> Policy<R, E> for &'static P
98where
99 P: Policy<R, E>,
100{
101 #[inline(always)]
102 fn retry(
103 &self,
104 req: Request<RetryBody>,
105 result: Result<R, E>,
106 ) -> impl Future<Output = PolicyResult<R, E>> + Send + '_ {
107 (**self).retry(req, result)
108 }
109
110 #[inline(always)]
111 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
112 (**self).clone_input(req)
113 }
114}
115
116impl<P, R, E> Policy<R, E> for std::sync::Arc<P>
117where
118 P: Policy<R, E>,
119{
120 #[inline(always)]
121 fn retry(
122 &self,
123
124 req: Request<RetryBody>,
125 result: Result<R, E>,
126 ) -> impl Future<Output = PolicyResult<R, E>> + Send + '_ {
127 (**self).retry(req, result)
128 }
129
130 #[inline(always)]
131 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
132 (**self).clone_input(req)
133 }
134}
135
136// TODO revisit PolicyResult after we remove Context concept
137// and see to be smarter about async fns in general
138
139/// The full result of a limit policy.
140#[expect(
141 clippy::large_enum_variant,
142 reason = "retry hands the request back without adding an allocation"
143)]
144pub enum PolicyResult<R, E> {
145 /// The result should not be retried,
146 /// and the result should be returned to the caller.
147 Abort(Result<R, E>),
148 /// The result should be retried,
149 /// and the request should be passed to the inner service again.
150 Retry {
151 /// The request to be retried, with the above context.
152 req: Request<RetryBody>,
153 },
154}
155
156impl<R, E> std::fmt::Debug for PolicyResult<R, E>
157where
158 R: std::fmt::Debug,
159 E: std::fmt::Debug,
160{
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 Self::Abort(err) => write!(f, "PolicyResult::Abort({err:?})"),
164 Self::Retry { req } => {
165 write!(f, "PolicyResult::Retry {{ req: {req:?} }}",)
166 }
167 }
168 }
169}
170
171macro_rules! impl_retry_policy_either {
172 ($id:ident, $($param:ident),+ $(,)?) => {
173 impl<$($param),+, Response, Error> Policy< Response, Error> for rama_core::combinators::$id<$($param),+>
174 where
175 $($param: Policy< Response, Error>),+,
176
177 Response: Send + 'static,
178 Error: Send + 'static,
179 {
180 async fn retry(
181 &self,
182 req: rama_http_types::Request<RetryBody>,
183 result: Result<Response, Error>,
184 ) -> PolicyResult< Response, Error> {
185 match self {
186 $(
187 rama_core::combinators::$id::$param(policy) => policy.retry(req, result).await,
188 )+
189 }
190 }
191
192 fn clone_input(
193 &self,
194 req: &rama_http_types::Request<RetryBody>,
195 ) -> Option<rama_http_types::Request<RetryBody>> {
196 match self {
197 $(
198 rama_core::combinators::$id::$param(policy) => policy.clone_input(req),
199 )+
200 }
201 }
202 }
203 };
204}
205
206rama_core::combinators::impl_either!(impl_retry_policy_either);