1use std::{fmt, marker::PhantomData};
7
8use ntex_service::{Ctx, IntoService, Middleware, Service};
9
10use crate::time::{Millis, timeout};
11
12#[derive(Debug)]
16pub struct Timeout<St> {
17 timeout: Millis,
18 _t: PhantomData<St>,
19}
20
21pub enum TimeoutError<E> {
23 Service(E),
25 Timeout,
27}
28
29impl<E> From<E> for TimeoutError<E> {
30 fn from(err: E) -> Self {
31 TimeoutError::Service(err)
32 }
33}
34
35impl<E: fmt::Debug> fmt::Debug for TimeoutError<E> {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 TimeoutError::Service(e) => write!(f, "TimeoutError::Service({e:?})"),
39 TimeoutError::Timeout => write!(f, "TimeoutError::Timeout"),
40 }
41 }
42}
43
44impl<E: fmt::Display> fmt::Display for TimeoutError<E> {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 TimeoutError::Service(e) => e.fmt(f),
48 TimeoutError::Timeout => write!(f, "Service call timeout"),
49 }
50 }
51}
52
53impl<E: fmt::Display + fmt::Debug> std::error::Error for TimeoutError<E> {}
54
55impl<E: PartialEq> PartialEq for TimeoutError<E> {
56 fn eq(&self, other: &TimeoutError<E>) -> bool {
57 match self {
58 TimeoutError::Service(e1) => match other {
59 TimeoutError::Service(e2) => e1 == e2,
60 TimeoutError::Timeout => false,
61 },
62 TimeoutError::Timeout => match other {
63 TimeoutError::Service(_) => false,
64 TimeoutError::Timeout => true,
65 },
66 }
67 }
68}
69
70impl<St> Timeout<St> {
71 pub fn new<T: Into<Millis>>(timeout: T) -> Self {
73 Timeout {
74 timeout: timeout.into(),
75 _t: PhantomData,
76 }
77 }
78}
79
80impl<St> Clone for Timeout<St> {
81 fn clone(&self) -> Self {
82 Timeout {
83 timeout: self.timeout,
84 _t: PhantomData,
85 }
86 }
87}
88
89impl<S, St> Middleware<S, St> for Timeout<St> {
90 type Service = TimeoutService<S, St>;
91
92 fn create(&self, _: &St, service: S) -> Self::Service {
93 TimeoutService {
94 service,
95 timeout: self.timeout,
96 st: PhantomData,
97 }
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct TimeoutService<S, St> {
104 service: S,
105 timeout: Millis,
106 st: PhantomData<St>,
107}
108
109impl<S, St> TimeoutService<S, St> {
110 pub fn new<T, Req>(timeout: T, service: impl IntoService<S, St, Req>) -> Self
112 where
113 T: Into<Millis>,
114 S: Service<St, Req>,
115 {
116 TimeoutService {
117 service: service.into_service(),
118 timeout: timeout.into(),
119 st: PhantomData,
120 }
121 }
122}
123
124impl<S, St, Req> Service<St, Req> for TimeoutService<S, St>
125where
126 S: Service<St, Req>,
127{
128 type Res = S::Res;
129 type Error = TimeoutError<S::Error>;
130
131 #[allow(
132 deprecated,
133 reason = "preserve validated dispatch and timeout boundaries during compatibility"
134 )]
135 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, Self::Error> {
136 if self.timeout.is_zero() {
137 ctx.call_nowait(&self.service, req)
138 .await
139 .map_err(TimeoutError::Service)
140 } else {
141 match timeout(self.timeout, ctx.call_nowait(&self.service, req)).await {
142 Ok(res) => res.map_err(TimeoutError::Service),
143 Err(()) => Err(TimeoutError::Timeout),
144 }
145 }
146 }
147
148 ntex_service::forward_bind!(St, service);
149 ntex_service::forward_ready!(St, service, TimeoutError::Service);
150 ntex_service::forward_shutdown!(St, service);
151}
152
153#[cfg(test)]
154mod tests {
155 use std::time::Duration;
156
157 use ntex_service::{Pipeline, apply, fn_factory};
158
159 use super::*;
160
161 #[derive(Clone, Debug, PartialEq)]
162 struct SleepService(Duration);
163
164 #[derive(Clone, Debug, PartialEq)]
165 struct SrvError;
166
167 impl fmt::Display for SrvError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "SrvError")
170 }
171 }
172
173 impl Service<(), ()> for SleepService {
174 type Res = ();
175 type Error = SrvError;
176
177 async fn call(&self, (): (), _: Ctx<'_, Self>) -> Result<(), SrvError> {
178 crate::time::sleep(self.0).await;
179 Ok::<_, SrvError>(())
180 }
181 }
182
183 #[ntex::test]
184 async fn test_success() {
185 let resolution = Duration::from_millis(100);
186 let wait_time = Duration::from_millis(50);
187
188 let timeout = Pipeline::new(
189 (),
190 TimeoutService::new(resolution, SleepService(wait_time)).clone(),
191 );
192 assert_eq!(timeout.call(()).await, Ok(()));
193 assert_eq!(timeout.ready().await, Ok(()));
194 timeout.shutdown().await;
195 }
196
197 #[ntex::test]
198 async fn test_zero() {
199 let wait_time = Duration::from_millis(50);
200 let resolution = Duration::from_millis(0);
201
202 let timeout = Pipeline::new((), TimeoutService::new(resolution, SleepService(wait_time)));
203 assert_eq!(timeout.call(()).await, Ok(()));
204 assert_eq!(timeout.ready().await, Ok(()));
205 }
206
207 #[ntex::test]
208 async fn test_timeout() {
209 let resolution = Duration::from_millis(100);
210 let wait_time = Duration::from_millis(500);
211
212 let timeout = Pipeline::new((), TimeoutService::new(resolution, SleepService(wait_time)));
213 assert_eq!(timeout.call(()).await, Err(TimeoutError::Timeout));
214 }
215
216 #[ntex::test]
217 #[allow(clippy::redundant_clone)]
218 async fn test_timeout_middleware() {
219 let resolution = Duration::from_millis(100);
220 let wait_time = Duration::from_millis(500);
221
222 let timeout = apply(
223 Timeout::new(resolution).clone(),
224 fn_factory(async move |()| Ok::<_, ()>(SleepService(wait_time))),
225 );
226 let srv = timeout.pipeline(()).await.unwrap();
227
228 let res = srv.call(()).await.unwrap_err();
229 assert_eq!(res, TimeoutError::Timeout);
230 }
231
232 #[test]
233 fn test_error() {
234 let err1 = TimeoutError::<SrvError>::Timeout;
235 assert!(format!("{err1:?}").contains("TimeoutError::Timeout"));
236 assert!(format!("{err1}").contains("Service call timeout"));
237
238 let err2: TimeoutError<_> = SrvError.into();
239 assert!(format!("{err2:?}").contains("TimeoutError::Service"));
240 assert!(format!("{err2}").contains("SrvError"));
241 }
242
243 #[test]
244 fn timeout_error_eq() {
245 assert_eq!(TimeoutError::Service(1), TimeoutError::Service(1));
246 assert_ne!(TimeoutError::Service(1), TimeoutError::Service(2));
247 assert_ne!(TimeoutError::Service(1), TimeoutError::Timeout);
248 assert_ne!(TimeoutError::Timeout, TimeoutError::Service(1));
249 assert_eq!(TimeoutError::<u8>::Timeout, TimeoutError::Timeout);
250 }
251}