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 #[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 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 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}