1use crate::{IntoPatterns, Resource, ResourceDef};
2
3#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4pub struct ResourceId(pub u16);
5
6pub struct Router<T, U = ()> {
15 routes: Vec<(ResourceDef, T, U)>,
16}
17
18impl<T, U> Router<T, U> {
19 pub fn build() -> RouterBuilder<T, U> {
21 RouterBuilder { routes: Vec::new() }
22 }
23
24 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 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 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 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
84pub struct RouterBuilder<T, U = ()> {
86 routes: Vec<(ResourceDef, T, U)>,
87}
88
89impl<T, U> RouterBuilder<T, U> {
90 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)] self.routes
102 .last_mut()
103 .map(|(rdef, val, ctx)| (rdef, val, ctx))
104 .unwrap()
105 }
106
107 pub fn finish(self) -> Router<T, U> {
109 Router {
110 routes: self.routes,
111 }
112 }
113}
114
115impl<T, U> RouterBuilder<T, U>
117where
118 U: Default,
119{
120 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 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 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 #[test]
149 fn test_recognizer_1() {
150 let mut router = Router::<usize>::build();
151 router.path("/name", 10).0.set_id(0);
152 router.path("/name/{val}", 11).0.set_id(1);
153 router.path("/name/{val}/index.html", 12).0.set_id(2);
154 router.path("/file/{file}.{ext}", 13).0.set_id(3);
155 router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
156 router.path("/v/{tail:.*}", 15).0.set_id(5);
157 router.path("/test2/{test}.html", 16).0.set_id(6);
158 router.path("/{test}/index.html", 17).0.set_id(7);
159 let mut router = router.finish();
160
161 let mut path = Path::new("/unknown");
162 assert!(router.recognize_mut(&mut path).is_none());
163
164 let mut path = Path::new("/name");
165 let (h, info) = router.recognize_mut(&mut path).unwrap();
166 assert_eq!(*h, 10);
167 assert_eq!(info, ResourceId(0));
168 assert!(path.is_empty());
169
170 let mut path = Path::new("/name/value");
171 let (h, info) = router.recognize_mut(&mut path).unwrap();
172 assert_eq!(*h, 11);
173 assert_eq!(info, ResourceId(1));
174 assert_eq!(path.get("val").unwrap(), "value");
175 assert_eq!(&path["val"], "value");
176
177 let mut path = Path::new("/name/value2/index.html");
178 let (h, info) = router.recognize_mut(&mut path).unwrap();
179 assert_eq!(*h, 12);
180 assert_eq!(info, ResourceId(2));
181 assert_eq!(path.get("val").unwrap(), "value2");
182
183 let mut path = Path::new("/file/file.gz");
184 let (h, info) = router.recognize_mut(&mut path).unwrap();
185 assert_eq!(*h, 13);
186 assert_eq!(info, ResourceId(3));
187 assert_eq!(path.get("file").unwrap(), "file");
188 assert_eq!(path.get("ext").unwrap(), "gz");
189
190 let mut path = Path::new("/v2/ttt/index.html");
191 let (h, info) = router.recognize_mut(&mut path).unwrap();
192 assert_eq!(*h, 14);
193 assert_eq!(info, ResourceId(4));
194 assert_eq!(path.get("val").unwrap(), "2");
195 assert_eq!(path.get("val2").unwrap(), "ttt");
196
197 let mut path = Path::new("/v/blah-blah/index.html");
198 let (h, info) = router.recognize_mut(&mut path).unwrap();
199 assert_eq!(*h, 15);
200 assert_eq!(info, ResourceId(5));
201 assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
202
203 let mut path = Path::new("/test2/index.html");
204 let (h, info) = router.recognize_mut(&mut path).unwrap();
205 assert_eq!(*h, 16);
206 assert_eq!(info, ResourceId(6));
207 assert_eq!(path.get("test").unwrap(), "index");
208
209 let mut path = Path::new("/bbb/index.html");
210 let (h, info) = router.recognize_mut(&mut path).unwrap();
211 assert_eq!(*h, 17);
212 assert_eq!(info, ResourceId(7));
213 assert_eq!(path.get("test").unwrap(), "bbb");
214 }
215
216 #[test]
217 fn test_recognizer_2() {
218 let mut router = Router::<usize>::build();
219 router.path("/index.json", 10);
220 router.path("/{source}.json", 11);
221 let mut router = router.finish();
222
223 let mut path = Path::new("/index.json");
224 let (h, _) = router.recognize_mut(&mut path).unwrap();
225 assert_eq!(*h, 10);
226
227 let mut path = Path::new("/test.json");
228 let (h, _) = router.recognize_mut(&mut path).unwrap();
229 assert_eq!(*h, 11);
230 }
231
232 #[test]
233 fn test_recognizer_with_prefix() {
234 let mut router = Router::<usize>::build();
235 router.path("/name", 10).0.set_id(0);
236 router.path("/name/{val}", 11).0.set_id(1);
237 let mut router = router.finish();
238
239 let mut path = Path::new("/name");
240 path.skip(5);
241 assert!(router.recognize_mut(&mut path).is_none());
242
243 let mut path = Path::new("/test/name");
244 path.skip(5);
245 let (h, _) = router.recognize_mut(&mut path).unwrap();
246 assert_eq!(*h, 10);
247
248 let mut path = Path::new("/test/name/value");
249 path.skip(5);
250 let (h, id) = router.recognize_mut(&mut path).unwrap();
251 assert_eq!(*h, 11);
252 assert_eq!(id, ResourceId(1));
253 assert_eq!(path.get("val").unwrap(), "value");
254 assert_eq!(&path["val"], "value");
255
256 let mut router = Router::<usize>::build();
258 router.path("/name", 10);
259 router.path("/name/{val}", 11);
260 let mut router = router.finish();
261
262 let mut path = Path::new("/name");
264 path.skip(6);
265 assert!(router.recognize_mut(&mut path).is_none());
266
267 let mut path = Path::new("/test2/name");
268 path.skip(6);
269 let (h, _) = router.recognize_mut(&mut path).unwrap();
270 assert_eq!(*h, 10);
271
272 let mut path = Path::new("/test2/name-test");
273 path.skip(6);
274 assert!(router.recognize_mut(&mut path).is_none());
275
276 let mut path = Path::new("/test2/name/ttt");
277 path.skip(6);
278 let (h, _) = router.recognize_mut(&mut path).unwrap();
279 assert_eq!(*h, 11);
280 assert_eq!(&path["val"], "ttt");
281 }
282}