1use ntex_service::{Ctx, Middleware, Service};
2
3pub trait Policy<S: Service<St, Req>, St, Req>: Sized + Clone {
5 async fn retry(&mut self, req: &Req, res: &Result<S::Res, S::Error>) -> bool;
7
8 fn clone_request(&self, req: &Req) -> Option<Req>;
12}
13
14#[derive(Clone, Debug)]
15pub struct Retry<P> {
19 policy: P,
20}
21
22#[derive(Clone, Debug)]
23pub struct RetryService<P, S> {
25 policy: P,
26 service: S,
27}
28
29impl<P> Retry<P> {
30 pub fn new(policy: P) -> Self {
32 Retry { policy }
33 }
34}
35
36impl<P: Clone, S, St> Middleware<S, St> for Retry<P> {
37 type Service = RetryService<P, S>;
38
39 fn create(&self, _: &St, service: S) -> Self::Service {
40 RetryService {
41 service,
42 policy: self.policy.clone(),
43 }
44 }
45}
46
47impl<P, S> RetryService<P, S> {
48 pub fn new(policy: P, service: S) -> Self {
50 RetryService { policy, service }
51 }
52}
53
54impl<P, S, St, Req> Service<St, Req> for RetryService<P, S>
55where
56 P: Policy<S, St, Req>,
57 S: Service<St, Req>,
58{
59 type Res = S::Res;
60 type Error = S::Error;
61
62 #[allow(
63 deprecated,
64 reason = "preserve the validated first-attempt bypass; retries use Ctx::call"
65 )]
66 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, S::Error> {
67 let mut policy = self.policy.clone();
68 let mut cloned = policy.clone_request(&req);
69 let mut result = ctx.call_nowait(&self.service, req).await;
71
72 while let Some(r) = cloned.take() {
73 if !policy.retry(&r, &result).await {
74 break;
75 }
76 cloned = policy.clone_request(&r);
77 result = ctx.call(&self.service, r).await;
78 }
79 result
80 }
81
82 ntex_service::forward_bind!(St, service);
83 ntex_service::forward_ready!(St, service);
84 ntex_service::forward_shutdown!(St, service);
85}
86
87#[derive(Copy, Clone, Debug)]
88pub struct DefaultRetryPolicy(u16);
92
93impl DefaultRetryPolicy {
94 pub fn new(retry: u16) -> Self {
96 DefaultRetryPolicy(retry)
97 }
98}
99
100impl Default for DefaultRetryPolicy {
101 fn default() -> Self {
102 DefaultRetryPolicy::new(3)
103 }
104}
105
106impl<S, St, Req> Policy<S, St, Req> for DefaultRetryPolicy
107where
108 S: Service<St, Req>,
109 Req: Clone,
110{
111 async fn retry(&mut self, _: &Req, res: &Result<S::Res, S::Error>) -> bool {
112 if res.is_err() {
113 if self.0 == 0 {
114 false
115 } else {
116 self.0 -= 1;
117 true
118 }
119 } else {
120 false
121 }
122 }
123
124 fn clone_request(&self, req: &Req) -> Option<Req> {
125 Some(req.clone())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 #![allow(clippy::unused_async_trait_impl)]
132 use std::{cell::Cell, rc::Rc};
133
134 use ntex_service::{Pipeline, apply, fn_factory};
135
136 use super::*;
137
138 #[derive(Clone, Debug, PartialEq)]
139 struct TestService(Rc<Cell<usize>>);
140
141 impl Service<(), ()> for TestService {
142 type Res = ();
143 type Error = ();
144
145 async fn call(&self, _r: (), _: Ctx<'_, Self>) -> Result<(), ()> {
146 let cnt = self.0.get();
147 if cnt == 0 {
148 Ok(())
149 } else {
150 self.0.set(cnt - 1);
151 Err(())
152 }
153 }
154 }
155
156 #[ntex::test]
157 async fn test_retry() {
158 let cnt = Rc::new(Cell::new(5));
159 let svc = Pipeline::new(
160 (),
161 RetryService::new(DefaultRetryPolicy::default(), TestService(cnt.clone())).clone(),
162 );
163 assert_eq!(svc.call(()).await, Err(()));
164 assert_eq!(svc.ready().await, Ok(()));
165 svc.shutdown().await;
166 assert_eq!(cnt.get(), 1);
167
168 let factory = apply(
169 Retry::new(DefaultRetryPolicy::new(3)).clone(),
170 fn_factory(|(): &()| async { Ok::<_, ()>(TestService(Rc::new(Cell::new(2)))) }),
171 );
172 let srv = factory.pipeline(()).await.unwrap();
173 assert_eq!(srv.call(()).await, Ok(()));
174
175 let factory = apply(
176 Retry::new(DefaultRetryPolicy::new(3)).clone(),
177 fn_factory(|(): &()| async { Ok::<_, ()>(TestService(Rc::new(Cell::new(2)))) }),
178 );
179 let srv = factory.pipeline(()).await.unwrap();
180 assert_eq!(srv.call(()).await, Ok(()));
181 }
182
183 #[derive(Clone)]
184 struct CloneOnce(Rc<Cell<usize>>);
185
186 impl Policy<TestService, (), ()> for CloneOnce {
187 async fn retry(&mut self, (): &(), res: &Result<(), ()>) -> bool {
188 res.is_err()
189 }
190
191 fn clone_request(&self, (): &()) -> Option<()> {
192 let n = self.0.get();
193 self.0.set(n + 1);
194 if n == 0 { Some(()) } else { None }
195 }
196 }
197
198 #[ntex::test]
199 async fn test_retry_without_clone() {
200 let cnt = Rc::new(Cell::new(5));
202 let clones = Rc::new(Cell::new(0));
203 let svc = Pipeline::new(
204 (),
205 RetryService::new(CloneOnce(clones.clone()), TestService(cnt.clone())),
206 );
207 assert_eq!(svc.call(()).await, Err(()));
208 assert_eq!(cnt.get(), 3);
209 assert_eq!(clones.get(), 2);
210 }
211}