1use super::{Policy, PolicyResult, RetryBody};
8use crate::{Request, Response};
9use rama_core::extensions::{Extension, ExtensionsRef};
10use rama_core::telemetry::tracing;
11use rama_utils::backoff::Backoff;
12
13#[derive(Debug, Clone, Default, Extension)]
14#[extension(tags(http))]
15#[non_exhaustive]
27pub struct DoNotRetry;
28
29#[derive(Debug, Clone)]
38pub struct ManagedPolicy<B = Undefined, C = Undefined, R = Undefined> {
39 backoff: B,
40 clone: C,
41 retry: R,
42}
43
44impl<B, C, R, Response, Error> Policy<Response, Error> for ManagedPolicy<B, C, R>
45where
46 B: Backoff,
47 C: CloneInput,
48 R: RetryRule<Request<RetryBody>, Response, Error>,
49 Response: Send + 'static,
50 Error: Send + 'static,
51{
52 async fn retry(
53 &self,
54 req: Request<RetryBody>,
55 result: Result<Response, Error>,
56 ) -> PolicyResult<Response, Error> {
57 if req.extensions().get_ref::<DoNotRetry>().is_some() {
58 return PolicyResult::Abort(result);
60 }
61
62 let (req, result, retry) = self.retry.retry(req, result).await;
63 if retry && self.backoff.next_backoff().await {
64 PolicyResult::Retry { req }
65 } else {
66 self.backoff.reset().await;
67 PolicyResult::Abort(result)
68 }
69 }
70
71 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
72 if req.extensions().get_ref::<DoNotRetry>().is_some() {
73 None
74 } else {
75 self.clone.clone_input(req)
76 }
77 }
78}
79
80impl Default for ManagedPolicy<Undefined, Undefined, Undefined> {
81 fn default() -> Self {
82 Self {
83 backoff: Undefined,
84 clone: Undefined,
85 retry: Undefined,
86 }
87 }
88}
89
90impl<F> ManagedPolicy<Undefined, Undefined, F> {
91 #[inline]
96 pub fn new(retry: F) -> Self {
97 ManagedPolicy::default().with_retry(retry)
98 }
99}
100
101impl<C, R> ManagedPolicy<Undefined, C, R> {
102 pub fn with_backoff<B>(self, backoff: B) -> ManagedPolicy<B, C, R> {
104 ManagedPolicy {
105 backoff,
106 clone: self.clone,
107 retry: self.retry,
108 }
109 }
110}
111
112impl<B, R> ManagedPolicy<B, Undefined, R> {
113 pub fn with_clone<C>(self, clone: C) -> ManagedPolicy<B, C, R> {
116 ManagedPolicy {
117 backoff: self.backoff,
118 clone,
119 retry: self.retry,
120 }
121 }
122}
123
124impl<B, C> ManagedPolicy<B, C, Undefined> {
125 pub fn with_retry<R>(self, retry: R) -> ManagedPolicy<B, C, R> {
128 ManagedPolicy {
129 backoff: self.backoff,
130 clone: self.clone,
131 retry,
132 }
133 }
134}
135
136pub trait RetryRule<Request, R, E>:
139 private::Sealed<(Request, R, E)> + Send + Sync + 'static
140{
141 fn retry(
143 &self,
144 request: Request,
145 result: Result<R, E>,
146 ) -> impl Future<Output = (Request, Result<R, E>, bool)> + Send + '_;
147}
148
149impl<Request, Body, E> RetryRule<Request, Response<Body>, E> for Undefined
150where
151 E: std::fmt::Debug + Send + Sync + 'static,
152 Body: Send + 'static,
153 Request: ExtensionsRef + Send + 'static,
154{
155 async fn retry(
156 &self,
157 request: Request,
158 result: Result<Response<Body>, E>,
159 ) -> (Request, Result<Response<Body>, E>, bool) {
160 match &result {
161 Ok(response) => {
162 let status = response.status();
163 if status.is_server_error() {
164 tracing::debug!(
165 "retrying server error http status code: {status} ({})",
166 status.as_u16()
167 );
168 (request, result, true)
169 } else {
170 (request, result, false)
171 }
172 }
173 Err(error) => {
174 tracing::debug!("retrying error: {:?}", error);
175 (request, result, true)
176 }
177 }
178 }
179}
180
181impl<F, Fut, Request, R, E> RetryRule<Request, R, E> for F
182where
183 F: Fn(Request, Result<R, E>) -> Fut + Send + Sync + 'static,
184 Fut: Future<Output = (Request, Result<R, E>, bool)> + Send + 'static,
185 Request: Send + 'static,
186 R: Send + 'static,
187 E: Send + Sync + 'static,
188{
189 async fn retry(&self, request: Request, result: Result<R, E>) -> (Request, Result<R, E>, bool) {
190 self(request, result).await
191 }
192}
193
194pub trait CloneInput: private::Sealed<()> + Send + Sync + 'static {
197 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>>;
203}
204
205impl CloneInput for Undefined {
206 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
207 Some(req.clone())
208 }
209}
210
211impl<F> CloneInput for F
212where
213 F: Fn(&Request<RetryBody>) -> Option<Request<RetryBody>> + Send + Sync + 'static,
214{
215 fn clone_input(&self, req: &Request<RetryBody>) -> Option<Request<RetryBody>> {
216 self(req)
217 }
218}
219
220#[derive(Debug, Clone)]
221#[non_exhaustive]
222pub struct Undefined;
226
227impl std::fmt::Display for Undefined {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 write!(f, "Undefined")
230 }
231}
232
233impl Backoff for Undefined {
234 async fn next_backoff(&self) -> bool {
235 true
236 }
237
238 async fn reset(&self) {}
239}
240
241mod private {
242 use super::*;
243
244 pub trait Sealed<S> {}
245
246 impl<S> Sealed<S> for Undefined {}
247 impl<F> Sealed<()> for F where
248 F: Fn(&Request<RetryBody>) -> Option<Request<RetryBody>> + Send + Sync + 'static
249 {
250 }
251 impl<F, Fut, Request, R, E> Sealed<(Request, R, E)> for F
252 where
253 F: Fn(Request, Result<R, E>) -> Fut + Send + Sync + 'static,
254 Fut: Future<Output = (Request, Result<R, E>, bool)> + Send + 'static,
255 {
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::{StatusCode, service::web::response::IntoResponse};
263 use rama_core::extensions::ExtensionsRef;
264 use rama_utils::{backoff::ExponentialBackoff, rng::HasherRng};
265 use std::time::Duration;
266
267 fn assert_clone_input_none(req: &Request<RetryBody>, policy: &impl Policy<Response, ()>) {
268 assert!(policy.clone_input(req).is_none());
269 }
270
271 fn assert_clone_input_some(req: &Request<RetryBody>, policy: &impl Policy<Response, ()>) {
272 assert!(policy.clone_input(req).is_some());
273 }
274
275 async fn assert_retry(
276 req: Request<RetryBody>,
277 result: Result<Response, ()>,
278 policy: &impl Policy<Response, ()>,
279 ) {
280 match policy.retry(req, result).await {
281 PolicyResult::Retry { .. } => (),
282 PolicyResult::Abort(_) => panic!("expected retry"),
283 };
284 }
285
286 async fn assert_abort(
287 req: Request<RetryBody>,
288 result: Result<Response, ()>,
289 policy: &impl Policy<Response, ()>,
290 ) {
291 match policy.retry(req, result).await {
292 PolicyResult::Retry { .. } => panic!("expected abort"),
293 PolicyResult::Abort(_) => (),
294 };
295 }
296
297 #[tokio::test]
298 async fn managed_policy_default() {
299 let request = Request::builder()
300 .method("GET")
301 .uri("http://example.com")
302 .body(RetryBody::empty())
303 .unwrap();
304
305 let policy = ManagedPolicy::default();
306
307 assert_clone_input_some(&request, &policy);
308
309 assert_abort(request.clone(), Ok(StatusCode::OK.into_response()), &policy).await;
311
312 assert_retry(
314 request.clone(),
315 Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
316 &policy,
317 )
318 .await;
319
320 assert_retry(request, Err(()), &policy).await;
322 }
323
324 #[tokio::test]
325 async fn managed_policy_default_do_not_retry() {
326 let req = Request::builder()
327 .method("GET")
328 .uri("http://example.com")
329 .body(RetryBody::empty())
330 .unwrap();
331
332 let policy = ManagedPolicy::default();
333
334 req.extensions().insert(DoNotRetry);
335
336 assert_clone_input_none(&req, &policy);
337
338 assert_abort(req.clone(), Ok(StatusCode::OK.into_response()), &policy).await;
340
341 assert_abort(
343 req.clone(),
344 Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
345 &policy,
346 )
347 .await;
348
349 assert_abort(req, Err(()), &policy).await;
351 }
352
353 #[tokio::test]
354 async fn test_policy_custom_clone_fn() {
355 let req = Request::builder()
356 .method("GET")
357 .uri("http://example.com")
358 .body(RetryBody::empty())
359 .unwrap();
360
361 fn clone_fn(_: &Request<RetryBody>) -> Option<Request<RetryBody>> {
362 None
363 }
364
365 let policy = ManagedPolicy::default().with_clone(clone_fn);
366
367 assert_clone_input_none(&req, &policy);
368
369 assert_abort(req, Ok(StatusCode::OK.into_response()), &policy).await;
371 }
372
373 #[test]
374 fn custom_clone_result_and_placeholder_display_are_preserved() {
375 let req = Request::builder()
376 .uri("http://example.com/original")
377 .body(RetryBody::empty())
378 .unwrap();
379 let clone = |req: &Request<RetryBody>| {
380 let mut cloned = req.clone();
381 *cloned.uri_mut() = "http://example.com/cloned".try_into().unwrap();
382 Some(cloned)
383 };
384
385 let cloned = CloneInput::clone_input(&clone, &req).unwrap();
386 assert_eq!(cloned.uri().as_str(), "http://example.com/cloned");
387 assert_eq!(Undefined.to_string(), "Undefined");
388 }
389
390 #[tokio::test]
391 async fn test_policy_custom_retry_fn() {
392 let req = Request::builder()
393 .method("GET")
394 .uri("http://example.com")
395 .body(RetryBody::empty())
396 .unwrap();
397
398 async fn retry_fn<Body, R, E>(
399 request: Request<Body>,
400 result: Result<R, E>,
401 ) -> (Request<Body>, Result<R, E>, bool) {
402 match result {
403 Ok(_) => (request, result, false),
404 Err(_) => (request, result, true),
405 }
406 }
407
408 let policy = ManagedPolicy::new(retry_fn);
409
410 assert_clone_input_some(&req, &policy);
412
413 assert_abort(req.clone(), Ok(StatusCode::OK.into_response()), &policy).await;
415 assert_abort(
416 req.clone(),
417 Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
418 &policy,
419 )
420 .await;
421 assert_retry(req, Err(()), &policy).await;
422 }
423
424 #[tokio::test]
425 async fn test_policy_fully_custom() {
426 let req = Request::builder()
427 .method("GET")
428 .uri("http://example.com")
429 .body(RetryBody::empty())
430 .unwrap();
431
432 fn clone_fn(_: &Request<RetryBody>) -> Option<Request<RetryBody>> {
433 None
434 }
435
436 async fn retry_fn<Body, R, E>(
437 req: Request<Body>,
438 result: Result<R, E>,
439 ) -> (Request<Body>, Result<R, E>, bool) {
440 match result {
441 Ok(_) => (req, result, false),
442 Err(_) => (req, result, true),
443 }
444 }
445
446 let backoff = ExponentialBackoff::new(
447 Duration::from_millis(1),
448 Duration::from_millis(5),
449 0.1,
450 HasherRng::default,
451 )
452 .unwrap();
453
454 let policy = ManagedPolicy::default()
455 .with_backoff(backoff)
456 .with_clone(clone_fn)
457 .with_retry(retry_fn);
458
459 assert_clone_input_none(&req, &policy);
460
461 assert_abort(req.clone(), Ok(StatusCode::OK.into_response()), &policy).await;
463 assert_abort(
464 req.clone(),
465 Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
466 &policy,
467 )
468 .await;
469 assert_retry(req, Err(()), &policy).await;
470 }
471}