actix_router/
router.rs

1use crate::{IntoPatterns, Resource, ResourceDef};
2
3#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4pub struct ResourceId(pub u16);
5
6/// Resource router.
7///
8/// It matches a [routing resource](Resource) to an ordered list of _routes_. Each is defined by a
9/// single [`ResourceDef`] and contains two types of custom data:
10/// 1. The route _value_, of the generic type `T`.
11/// 1. Some _context_ data, of the generic type `U`, which is only provided to the check function in
12///    [`recognize_fn`](Self::recognize_fn). This parameter defaults to `()` and can be omitted if
13///    not required.
14pub struct Router<T, U = ()> {
15    routes: Vec<(ResourceDef, T, U)>,
16}
17
18impl<T, U> Router<T, U> {
19    /// Constructs new `RouterBuilder` with empty route list.
20    pub fn build() -> RouterBuilder<T, U> {
21        RouterBuilder { routes: Vec::new() }
22    }
23
24    /// Finds the value in the router that matches a given [routing resource](Resource).
25    ///
26    /// The match result, including the captured dynamic segments, in the `resource`.
27    pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
28    where
29        R: Resource,
30    {
31        self.recognize_fn(resource, |_, _| true)
32    }
33
34    /// Same as [`recognize`](Self::recognize) but returns a mutable reference to the matched value.
35    pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
36    where
37        R: Resource,
38    {
39        self.recognize_mut_fn(resource, |_, _| true)
40    }
41
42    /// Finds the value in the router that matches a given [routing resource](Resource) and passes
43    /// an additional predicate check using context data.
44    ///
45    /// Similar to [`recognize`](Self::recognize). However, before accepting the route as matched,
46    /// the `check` closure is executed, passing the resource and each route's context data. If the
47    /// closure returns true then the match result is stored into `resource` and a reference to
48    /// the matched _value_ is returned.
49    pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)>
50    where
51        R: Resource,
52        F: FnMut(&R, &U) -> bool,
53    {
54        for (rdef, val, ctx) in self.routes.iter() {
55            if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
56                return Some((val, ResourceId(rdef.id())));
57            }
58        }
59
60        None
61    }
62
63    /// Same as [`recognize_fn`](Self::recognize_fn) but returns a mutable reference to the matched
64    /// value.
65    pub fn recognize_mut_fn<R, F>(
66        &mut self,
67        resource: &mut R,
68        mut check: F,
69    ) -> Option<(&mut T, ResourceId)>
70    where
71        R: Resource,
72        F: FnMut(&R, &U) -> bool,
73    {
74        for (rdef, val, ctx) in self.routes.iter_mut() {
75            if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
76                return Some((val, ResourceId(rdef.id())));
77            }
78        }
79
80        None
81    }
82}
83
84/// Builder for an ordered [routing](Router) list.
85pub struct RouterBuilder<T, U = ()> {
86    routes: Vec<(ResourceDef, T, U)>,
87}
88
89impl<T, U> RouterBuilder<T, U> {
90    /// Adds a new route to the end of the routing list.
91    ///
92    /// Returns mutable references to elements of the new route.
93    pub fn push(
94        &mut self,
95        rdef: ResourceDef,
96        val: T,
97        ctx: U,
98    ) -> (&mut ResourceDef, &mut T, &mut U) {
99        self.routes.push((rdef, val, ctx));
100        #[allow(clippy::map_identity)] // map is used to distribute &mut-ness to tuple elements
101        self.routes
102            .last_mut()
103            .map(|(rdef, val, ctx)| (rdef, val, ctx))
104            .unwrap()
105    }
106
107    /// Finish configuration and create router instance.
108    pub fn finish(self) -> Router<T, U> {
109        Router {
110            routes: self.routes,
111        }
112    }
113}
114
115/// Convenience methods provided when context data impls [`Default`]
116impl<T, U> RouterBuilder<T, U>
117where
118    U: Default,
119{
120    /// Registers resource for specified path.
121    pub fn path(&mut self, path: impl IntoPatterns, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
122        self.push(ResourceDef::new(path), val, U::default())
123    }
124
125    /// Registers resource for specified path prefix.
126    pub fn prefix(
127        &mut self,
128        prefix: impl IntoPatterns,
129        val: T,
130    ) -> (&mut ResourceDef, &mut T, &mut U) {
131        self.push(ResourceDef::prefix(prefix), val, U::default())
132    }
133
134    /// Registers resource for [`ResourceDef`].
135    pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
136        self.push(rdef, val, U::default())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::{
143        path::Path,
144        router::{ResourceId, Router},
145    };
146
147    #[allow(clippy::cognitive_complexity)]
148    #[allow(clippy::literal_string_with_formatting_args)]
149    #[test]
150    fn test_recognizer_1() {
151        let mut router = Router::<usize>::build();
152        router.path("/name", 10).0.set_id(0);
153        router.path("/name/{val}", 11).0.set_id(1);
154        router.path("/name/{val}/index.html", 12).0.set_id(2);
155        router.path("/file/{file}.{ext}", 13).0.set_id(3);
156        router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
157        router.path("/v/{tail:.*}", 15).0.set_id(5);
158        router.path("/test2/{test}.html", 16).0.set_id(6);
159        router.path("/{test}/index.html", 17).0.set_id(7);
160        let mut router = router.finish();
161
162        let mut path = Path::new("/unknown");
163        assert!(router.recognize_mut(&mut path).is_none());
164
165        let mut path = Path::new("/name");
166        let (h, info) = router.recognize_mut(&mut path).unwrap();
167        assert_eq!(*h, 10);
168        assert_eq!(info, ResourceId(0));
169        assert!(path.is_empty());
170
171        let mut path = Path::new("/name/value");
172        let (h, info) = router.recognize_mut(&mut path).unwrap();
173        assert_eq!(*h, 11);
174        assert_eq!(info, ResourceId(1));
175        assert_eq!(path.get("val").unwrap(), "value");
176        assert_eq!(&path["val"], "value");
177
178        let mut path = Path::new("/name/value2/index.html");
179        let (h, info) = router.recognize_mut(&mut path).unwrap();
180        assert_eq!(*h, 12);
181        assert_eq!(info, ResourceId(2));
182        assert_eq!(path.get("val").unwrap(), "value2");
183
184        let mut path = Path::new("/file/file.gz");
185        let (h, info) = router.recognize_mut(&mut path).unwrap();
186        assert_eq!(*h, 13);
187        assert_eq!(info, ResourceId(3));
188        assert_eq!(path.get("file").unwrap(), "file");
189        assert_eq!(path.get("ext").unwrap(), "gz");
190
191        let mut path = Path::new("/v2/ttt/index.html");
192        let (h, info) = router.recognize_mut(&mut path).unwrap();
193        assert_eq!(*h, 14);
194        assert_eq!(info, ResourceId(4));
195        assert_eq!(path.get("val").unwrap(), "2");
196        assert_eq!(path.get("val2").unwrap(), "ttt");
197
198        let mut path = Path::new("/v/blah-blah/index.html");
199        let (h, info) = router.recognize_mut(&mut path).unwrap();
200        assert_eq!(*h, 15);
201        assert_eq!(info, ResourceId(5));
202        assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
203
204        let mut path = Path::new("/test2/index.html");
205        let (h, info) = router.recognize_mut(&mut path).unwrap();
206        assert_eq!(*h, 16);
207        assert_eq!(info, ResourceId(6));
208        assert_eq!(path.get("test").unwrap(), "index");
209
210        let mut path = Path::new("/bbb/index.html");
211        let (h, info) = router.recognize_mut(&mut path).unwrap();
212        assert_eq!(*h, 17);
213        assert_eq!(info, ResourceId(7));
214        assert_eq!(path.get("test").unwrap(), "bbb");
215    }
216
217    #[test]
218    fn test_recognizer_2() {
219        let mut router = Router::<usize>::build();
220        router.path("/index.json", 10);
221        router.path("/{source}.json", 11);
222        let mut router = router.finish();
223
224        let mut path = Path::new("/index.json");
225        let (h, _) = router.recognize_mut(&mut path).unwrap();
226        assert_eq!(*h, 10);
227
228        let mut path = Path::new("/test.json");
229        let (h, _) = router.recognize_mut(&mut path).unwrap();
230        assert_eq!(*h, 11);
231    }
232
233    #[test]
234    fn test_recognizer_with_prefix() {
235        let mut router = Router::<usize>::build();
236        router.path("/name", 10).0.set_id(0);
237        router.path("/name/{val}", 11).0.set_id(1);
238        let mut router = router.finish();
239
240        let mut path = Path::new("/name");
241        path.skip(5);
242        assert!(router.recognize_mut(&mut path).is_none());
243
244        let mut path = Path::new("/test/name");
245        path.skip(5);
246        let (h, _) = router.recognize_mut(&mut path).unwrap();
247        assert_eq!(*h, 10);
248
249        let mut path = Path::new("/test/name/value");
250        path.skip(5);
251        let (h, id) = router.recognize_mut(&mut path).unwrap();
252        assert_eq!(*h, 11);
253        assert_eq!(id, ResourceId(1));
254        assert_eq!(path.get("val").unwrap(), "value");
255        assert_eq!(&path["val"], "value");
256
257        // same patterns
258        let mut router = Router::<usize>::build();
259        router.path("/name", 10);
260        router.path("/name/{val}", 11);
261        let mut router = router.finish();
262
263        // test skip beyond path length
264        let mut path = Path::new("/name");
265        path.skip(6);
266        assert!(router.recognize_mut(&mut path).is_none());
267
268        let mut path = Path::new("/test2/name");
269        path.skip(6);
270        let (h, _) = router.recognize_mut(&mut path).unwrap();
271        assert_eq!(*h, 10);
272
273        let mut path = Path::new("/test2/name-test");
274        path.skip(6);
275        assert!(router.recognize_mut(&mut path).is_none());
276
277        let mut path = Path::new("/test2/name/ttt");
278        path.skip(6);
279        let (h, _) = router.recognize_mut(&mut path).unwrap();
280        assert_eq!(*h, 11);
281        assert_eq!(&path["val"], "ttt");
282    }
283}