1use std::{fmt, marker::PhantomData};
6
7use ntex_service::{Ctx, IntoService, Middleware, Service};
8
9use crate::future::{Either, select};
10use crate::time::{Millis, sleep};
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 {
72 Timeout {
73 timeout: timeout.into(),
74 _t: PhantomData,
75 }
76 }
77}
78
79impl<St> Clone for Timeout<St> {
80 fn clone(&self) -> Self {
81 Timeout {
82 timeout: self.timeout,
83 _t: PhantomData,
84 }
85 }
86}
87
88impl<S, St> Middleware<S, St> for Timeout<St> {
89 type Service = TimeoutService<S, St>;
90
91 fn create(&self, _: &St, service: S) -> Self::Service {
92 TimeoutService {
93 service,
94 timeout: self.timeout,
95 st: PhantomData,
96 }
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct TimeoutService<S, St> {
103 service: S,
104 timeout: Millis,
105 st: PhantomData<St>,
106}
107
108impl<S, St> TimeoutService<S, St> {
109 pub fn new<T, Req>(timeout: T, service: impl IntoService<S, St, Req>) -> Self
110 where
111 T: Into<Millis>,
112 S: Service<St, Req>,
113 {
114 TimeoutService {
115 service: service.into_service(),
116 timeout: timeout.into(),
117 st: PhantomData,
118 }
119 }
120}
121
122impl<S, St, Req> Service<St, Req> for TimeoutService<S, St>
123where
124 S: Service<St, Req>,
125{
126 type Res = S::Res;
127 type Error = TimeoutError<S::Error>;
128
129 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, Self::Error> {
130 if self.timeout.is_zero() {
131 ctx.call(&self.service, req)
132 .await
133 .map_err(TimeoutError::Service)
134 } else {
135 match select(sleep(self.timeout), ctx.call(&self.service, req)).await {
136 Either::Left(()) => Err(TimeoutError::Timeout),
137 Either::Right(res) => res.map_err(TimeoutError::Service),
138 }
139 }
140 }
141
142 ntex_service::forward_ready!(St, service, TimeoutError::Service);
143 ntex_service::forward_shutdown!(St, service);
144}
145
146#[cfg(test)]
147mod tests {
148 use std::time::Duration;
149
150 use ntex_service::{Pipeline, apply, fn_factory};
151
152 use super::*;
153
154 #[derive(Clone, Debug, PartialEq)]
155 struct SleepService(Duration);
156
157 #[derive(Clone, Debug, PartialEq)]
158 struct SrvError;
159
160 impl fmt::Display for SrvError {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 write!(f, "SrvError")
163 }
164 }
165
166 impl Service<(), ()> for SleepService {
167 type Res = ();
168 type Error = SrvError;
169
170 async fn call(&self, (): (), _: Ctx<'_, Self>) -> Result<(), SrvError> {
171 crate::time::sleep(self.0).await;
172 Ok::<_, SrvError>(())
173 }
174 }
175
176 #[ntex::test]
177 async fn test_success() {
178 let resolution = Duration::from_millis(100);
179 let wait_time = Duration::from_millis(50);
180
181 let timeout = Pipeline::new(
182 (),
183 TimeoutService::new(resolution, SleepService(wait_time)).clone(),
184 );
185 assert_eq!(timeout.call(()).await, Ok(()));
186 assert_eq!(timeout.ready().await, Ok(()));
187 timeout.shutdown().await;
188 }
189
190 #[ntex::test]
191 async fn test_zero() {
192 let wait_time = Duration::from_millis(50);
193 let resolution = Duration::from_millis(0);
194
195 let timeout = Pipeline::new((), TimeoutService::new(resolution, SleepService(wait_time)));
196 assert_eq!(timeout.call(()).await, Ok(()));
197 assert_eq!(timeout.ready().await, Ok(()));
198 }
199
200 #[ntex::test]
201 async fn test_timeout() {
202 let resolution = Duration::from_millis(100);
203 let wait_time = Duration::from_millis(500);
204
205 let timeout = Pipeline::new((), TimeoutService::new(resolution, SleepService(wait_time)));
206 assert_eq!(timeout.call(()).await, Err(TimeoutError::Timeout));
207 }
208
209 #[ntex::test]
210 #[allow(clippy::redundant_clone)]
211 async fn test_timeout_middleware() {
212 let resolution = Duration::from_millis(100);
213 let wait_time = Duration::from_millis(500);
214
215 let timeout = apply(
216 Timeout::new(resolution).clone(),
217 fn_factory(async move |()| Ok::<_, ()>(SleepService(wait_time))),
218 );
219 let srv = timeout.pipeline(()).await.unwrap();
220
221 let res = srv.call(()).await.unwrap_err();
222 assert_eq!(res, TimeoutError::Timeout);
223 }
224
225 #[test]
226 fn test_error() {
227 let err1 = TimeoutError::<SrvError>::Timeout;
228 assert!(format!("{err1:?}").contains("TimeoutError::Timeout"));
229 assert!(format!("{err1}").contains("Service call timeout"));
230
231 let err2: TimeoutError<_> = SrvError.into();
232 assert!(format!("{err2:?}").contains("TimeoutError::Service"));
233 assert!(format!("{err2}").contains("SrvError"));
234 }
235}