Skip to main content

router_service/unsync/
mod.rs

1//! An unsynchronized router that can be used as a [`Service`](tower::Service).
2use std::future::Future;
3use std::sync::RwLock;
4use std::{collections::HashMap, sync::Arc};
5
6use http::{Method, Request, Response};
7use matchit::Router as MatchRouter;
8
9use crate::handler::*;
10
11pub use crate::service::ResponseFuture;
12
13mod service;
14
15#[derive(Default)]
16struct Route<Body, Data, Error> {
17    handlers: HashMap<Method, AsyncUnsyncHandler<Body, Data, Error>>,
18    catchall: Option<AsyncUnsyncHandler<Body, Data, Error>>,
19}
20
21/// A router that can be used as a [`Service`](tower::Service).
22///
23/// # Example
24/// ```
25/// # futures::executor::block_on(async move {
26/// use std::convert::Infallible;
27///
28/// use http::{Request, Response, StatusCode};
29/// use tower::Service;
30/// use router_service::unsync::Router;
31///
32/// let mut router = Router::new()
33///     .get("/", |_, _| async move {
34///         Response::builder().body(())
35///     });
36///
37/// let req = Request::get("/").body(()).unwrap();
38/// let resp = router.call(req).await.unwrap();
39/// assert_eq!(resp.status(), 200);
40/// # });
41/// ```
42#[derive(Default)]
43pub struct Router<Body, Data: Clone, Error> {
44    inner: Arc<RwLock<MatchRouter<Route<Body, Data, Error>>>>,
45    data: Data,
46}
47
48impl<Body, Error> Router<Body, (), Error> {
49    /// Create a new router that doesn't require any data to be passed to handlers.
50    pub fn new() -> Self {
51        Self {
52            inner: Default::default(),
53            data: (),
54        }
55    }
56}
57
58impl<Body, Data, Error> Router<Body, Data, Error>
59where
60    Body: 'static,
61    Data: Clone + 'static,
62    Error: 'static,
63{
64    /// Create a new router that requires data to be passed to handlers.
65    ///
66    /// # Example
67    /// ```
68    /// # futures::executor::block_on(async move {
69    /// use std::convert::Infallible;
70    ///
71    /// use http::{Request, Response, StatusCode};
72    /// use tower::Service;
73    /// use router_service::unsync::Router;
74    ///
75    /// let mut router = Router::with_data(42)
76    ///     .get("/", |_, data| async move {
77    ///         println!("Got data! {}", data.data);
78    ///         Response::builder().body(())
79    ///     });
80    ///
81    /// let req = Request::get("/").body(()).unwrap();
82    /// let resp = router.call(req).await.unwrap();
83    /// assert_eq!(resp.status(), 200);
84    /// # });
85    /// ```
86    pub fn with_data(data: Data) -> Self {
87        Self {
88            inner: Default::default(),
89            data,
90        }
91    }
92
93    /// Registers a route requiring the `GET` method.
94    pub fn get<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
95    where
96        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
97        HandlerFn: 'static,
98        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
99    {
100        self.insert_handler(path, Method::GET, handler)
101    }
102
103    /// Registers a route requiring the `POST` method.
104    pub fn post<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
105    where
106        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
107        HandlerFn: 'static,
108        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
109    {
110        self.insert_handler(path, Method::POST, handler)
111    }
112
113    /// Registers a route requiring the `PUT` method.
114    pub fn put<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
115    where
116        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
117        HandlerFn: 'static,
118        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
119    {
120        self.insert_handler(path, Method::PUT, handler)
121    }
122
123    /// Registers a route requiring the `DELETE` method.
124    pub fn delete<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
125    where
126        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
127        HandlerFn: 'static,
128        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
129    {
130        self.insert_handler(path, Method::DELETE, handler)
131    }
132
133    /// Registers a route requiring the `HEAD` method.
134    pub fn head<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
135    where
136        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
137        HandlerFn: 'static,
138        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
139    {
140        self.insert_handler(path, Method::HEAD, handler)
141    }
142
143    /// Registers a route requiring the `OPTIONS` method.
144    pub fn options<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
145    where
146        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
147        HandlerFn: 'static,
148        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
149    {
150        self.insert_handler(path, Method::DELETE, handler)
151    }
152
153    /// Registers a route requiring the `PATCH` method.
154    pub fn patch<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
155    where
156        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
157        HandlerFn: 'static,
158        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
159    {
160        self.insert_handler(path, Method::PATCH, handler)
161    }
162
163    /// Registers a route matching any method.
164    pub fn any<HandlerFn, Fut>(self, path: impl AsRef<str>, handler: HandlerFn) -> Self
165    where
166        HandlerFn: Fn(Request<Body>, RouteContext<Data>) -> Fut,
167        HandlerFn: 'static,
168        Fut: Future<Output = Result<Response<Body>, Error>> + 'static,
169    {
170        let mut inner = self.inner.write().unwrap();
171
172        if let Ok(existing) = inner.at_mut(path.as_ref()) {
173            existing.value.catchall = Some(handler.into());
174        } else {
175            inner
176                .insert(
177                    path.as_ref(),
178                    Route {
179                        handlers: HashMap::new(),
180                        catchall: Some(handler.into()),
181                    },
182                )
183                .expect("unable to add route to router");
184        }
185
186        drop(inner);
187
188        self
189    }
190
191    fn insert_handler<H>(self, path: impl AsRef<str>, method: Method, handler: H) -> Self
192    where
193        H: Into<AsyncUnsyncHandler<Body, Data, Error>>,
194    {
195        let mut inner = self.inner.write().unwrap();
196        if let Ok(existing) = inner.at_mut(path.as_ref()) {
197            existing.value.handlers.insert(method, handler.into());
198        } else {
199            let mut handlers: HashMap<Method, AsyncUnsyncHandler<Body, Data, Error>> =
200                HashMap::new();
201            handlers.insert(method, handler.into());
202
203            inner
204                .insert(
205                    path.as_ref(),
206                    Route {
207                        handlers,
208                        catchall: None,
209                    },
210                )
211                .expect("unable to add route to router");
212        }
213
214        drop(inner);
215
216        self
217    }
218}
219
220impl<Body, Data, Error> Clone for Router<Body, Data, Error>
221where
222    Data: Clone,
223{
224    fn clone(&self) -> Self {
225        Self {
226            inner: self.inner.clone(),
227            data: self.data.clone(),
228        }
229    }
230}
231
232/// The context of a matched route.
233#[derive(Debug)]
234pub struct RouteContext<T> {
235    /// Arbitrary data associated with the router that is available to all handlers.
236    pub data: T,
237    params: HashMap<String, String>,
238}
239
240impl<T> RouteContext<T> {
241    /// Returns a parameter value from the path by name.
242    pub fn param(&self, name: impl AsRef<str>) -> Option<&str> {
243        self.params.get(name.as_ref()).map(|s| s.as_str())
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use std::{
250        convert::Infallible,
251        sync::{
252            atomic::{AtomicBool, Ordering},
253            Arc,
254        },
255    };
256
257    use http::{Method, Request, Response};
258    use tower::Service;
259
260    use crate::unsync::Router;
261
262    #[test]
263    fn not_found() {
264        futures::executor::block_on(async move {
265            let mut router: Router<(), (), Infallible> = Router::new();
266            let req = Request::builder()
267                .uri("/not-found")
268                .method(Method::GET)
269                .body(())
270                .unwrap();
271
272            let resp = router.call(req).await;
273            let resp = resp.unwrap();
274
275            assert_eq!(resp.status(), 404);
276        });
277    }
278
279    #[test]
280    fn receives_data() {
281        futures::executor::block_on(async move {
282            let data = Arc::new(AtomicBool::new(false));
283            let mut router: Router<(), Arc<AtomicBool>, Infallible> =
284                Router::with_data(data.clone()).get("/example", |_req, ctx| async move {
285                    ctx.data.store(true, Ordering::SeqCst);
286                    Ok(Response::builder().body(()).unwrap())
287                });
288
289            let req = Request::builder()
290                .uri("/example")
291                .method(Method::GET)
292                .body(())
293                .unwrap();
294
295            let resp = router.call(req).await.unwrap();
296            assert_eq!(resp.status(), 200);
297            assert!(data.load(Ordering::SeqCst));
298        });
299    }
300
301    #[test]
302    fn receives_data_async() {
303        futures::executor::block_on(async move {
304            let data = Arc::new(AtomicBool::new(false));
305            let mut router: Router<_, _, Infallible> =
306                Router::with_data(data.clone()).get("/example", |_req, ctx| async move {
307                    ctx.data.store(true, Ordering::SeqCst);
308                    Ok(Response::builder().body(()).unwrap())
309                });
310
311            let req = Request::builder()
312                .uri("/example")
313                .method(Method::GET)
314                .body(())
315                .unwrap();
316
317            let resp = router.call(req).await.unwrap();
318            assert_eq!(resp.status(), 200);
319            assert!(data.load(Ordering::SeqCst));
320        });
321    }
322}