1use std::collections::HashMap;
4
5#[derive(Clone)]
14pub struct PathTable<R> {
15 accept: Option<R>,
16 next: HashMap<String, PathTable<R>>,
17 wildcard: Option<Box<Wildcard<R>>>,
18}
19
20#[derive(Copy, Clone, PartialEq)]
21enum WildcardKind {
22 Segment,
23 CatchAll,
24}
25
26impl std::fmt::Display for WildcardKind {
27 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
28 match self {
29 WildcardKind::Segment => Ok(()),
30 WildcardKind::CatchAll => write!(fmt, "*"),
31 }
32 }
33}
34
35#[derive(Clone)]
36struct Wildcard<R> {
37 name: String,
38 count_mod: WildcardKind,
39 table: PathTable<R>,
40}
41
42#[derive(Debug)]
44pub struct RouteMatch<'a> {
45 pub vec: Vec<&'a str>,
47 pub map: HashMap<&'a str, &'a str>,
49}
50
51impl<R> std::fmt::Debug for PathTable<R> {
52 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
53 struct Children<'a, R>(
54 &'a HashMap<String, PathTable<R>>,
55 Option<&'a Wildcard<R>>,
56 );
57 impl<'a, R> std::fmt::Debug for Children<'a, R> {
58 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
59 let mut dbg = fmt.debug_map();
60 dbg.entries(self.0.iter());
61 if let Some(wildcard) = self.1 {
62 dbg.entry(
63 &format_args!("{{{}}}{}", wildcard.name, wildcard.count_mod),
64 &wildcard.table,
65 );
66 }
67 dbg.finish()
68 }
69 }
70
71 fmt
72 .debug_struct("PathTable")
73 .field(
74 "resource",
75 &format_args!(
76 "{}",
77 if self.accept.is_some() {
78 "Some"
79 } else {
80 "None"
81 }
82 ),
83 )
84 .field(
85 "children",
86 &Children(&self.next, self.wildcard.as_ref().map(|x| &**x)),
87 )
88 .finish()
89 }
90}
91
92impl<R> Default for PathTable<R> {
93 fn default() -> Self {
94 PathTable::new()
95 }
96}
97
98impl<R> PathTable<R> {
99 pub fn new() -> PathTable<R> {
101 PathTable {
102 accept: None,
103 next: HashMap::new(),
104 wildcard: None,
105 }
106 }
107
108 pub fn resource(&self) -> Option<&R> {
110 self.accept.as_ref()
111 }
112
113 pub fn resource_mut(&mut self) -> &mut Option<R> {
115 &mut self.accept
116 }
117
118 pub fn iter(&self) -> Resources<R> {
120 Resources { stack: vec![self] }
121 }
122
123 pub fn iter_mut(&mut self) -> ResourcesMut<R> {
125 ResourcesMut { stack: vec![self] }
126 }
127
128 pub fn route<'a>(&'a self, path: &'a str) -> Option<(&'a R, RouteMatch<'a>)> {
130 let mut table = self;
131 let mut params = Vec::new();
132 let mut param_map = HashMap::new();
133
134 let segment_iter = path
136 .match_indices('/')
137 .chain(std::iter::once((path.len(), "")))
138 .scan(0usize, |prev_idx, (idx, _)| {
139 let starts_at = *prev_idx;
140 let segment = &path[starts_at..idx];
141 *prev_idx = idx + 1;
142 Some((starts_at, segment))
143 });
144
145 for (starts_at, mut segment) in segment_iter {
146 if segment.is_empty() {
147 continue;
148 }
149
150 if let Some(next_table) = table.next.get(segment) {
151 table = next_table;
152 } else if let Some(wildcard) = &table.wildcard {
153 let last = if wildcard.count_mod == WildcardKind::CatchAll {
154 segment = &path[starts_at..];
155 true
156 } else {
157 false
158 };
159
160 params.push(segment);
161
162 if !wildcard.name.is_empty() {
163 param_map.insert(&*wildcard.name, segment);
164 }
165
166 table = &wildcard.table;
167
168 if last {
169 break;
170 }
171 } else {
172 return None;
173 }
174 }
175
176 if table.accept.is_none() {
177 if let Some(wildcard) = &table.wildcard {
178 if wildcard.count_mod == WildcardKind::CatchAll {
179 params.push("");
180
181 if !wildcard.name.is_empty() {
182 param_map.insert(&*wildcard.name, "");
183 }
184
185 table = &wildcard.table;
186 }
187 }
188 }
189
190 table.accept.as_ref().map(|res| {
191 (
192 res,
193 RouteMatch {
194 vec: params,
195 map: param_map,
196 },
197 )
198 })
199 }
200
201 fn wildcard_mut(&mut self) -> Option<&mut Wildcard<R>> {
202 self.wildcard.as_mut().map(|b| &mut **b)
203 }
204
205 pub fn setup_table(&mut self, path: &str) -> &mut PathTable<R> {
209 let mut table = self;
210 let mut forbid_next = false;
211 for segment in path.split('/') {
212 if segment.is_empty() {
213 continue;
214 }
215
216 if forbid_next {
217 panic!("No segments are allowed after wildcard with `*` modifier");
218 }
219
220 let wildcard_opt = if segment.starts_with('{') {
221 if segment.ends_with('}') {
222 Some((&segment[1..segment.len() - 1], WildcardKind::Segment))
223 } else if segment.ends_with("}*") {
224 Some((&segment[1..segment.len() - 2], WildcardKind::CatchAll))
225 } else {
226 None
227 }
228 } else if segment == "*" {
229 Some(("", WildcardKind::CatchAll))
230 } else {
231 None
232 };
233
234 if let Some((name, count_mod)) = wildcard_opt {
235 if count_mod != WildcardKind::Segment {
236 forbid_next = true;
237 }
238
239 if table.wildcard.is_none() {
240 table.wildcard = Some(Box::new(Wildcard {
241 name: name.to_string(),
242 count_mod,
243 table: PathTable::new(),
244 }));
245 }
246
247 match table.wildcard_mut().unwrap() {
248 Wildcard {
249 name: n,
250 count_mod: c,
251 ..
252 } if name != n || count_mod != *c => {
253 panic!(
254 "Route {} segment `{{{}}}{}` conflicts with existing wildcard segment `{{{}}}{}`",
255 path, name, count_mod, n, c
256 );
257 }
258 Wildcard { table: t, .. } => {
259 table = t;
260 }
261 }
262 } else {
263 table = table
264 .next
265 .entry(segment.to_string())
266 .or_insert_with(PathTable::new);
267 }
268 }
269
270 table
271 }
272}
273
274impl<R: Default> PathTable<R> {
275 pub fn setup(&mut self, path: &str) -> &mut R {
277 let table = self.setup_table(path);
278
279 if table.accept.is_none() {
280 table.accept = Some(R::default())
281 }
282
283 table.accept.as_mut().unwrap()
284 }
285}
286
287pub struct Resources<'a, R> {
289 stack: Vec<&'a PathTable<R>>,
290}
291
292impl<'a, R> Iterator for Resources<'a, R> {
293 type Item = &'a R;
294
295 fn next(&mut self) -> Option<&'a R> {
296 while let Some(table) = self.stack.pop() {
297 self.stack.extend(table.next.values());
298 if let Some(wildcard) = table.wildcard.as_ref() {
299 self.stack.push(&wildcard.table);
300 }
301 if let Some(res) = &table.accept {
302 return Some(res);
303 }
304 }
305 None
306 }
307}
308
309pub struct ResourcesMut<'a, R> {
311 stack: Vec<&'a mut PathTable<R>>,
312}
313
314impl<'a, R> Iterator for ResourcesMut<'a, R> {
315 type Item = &'a mut R;
316
317 fn next(&mut self) -> Option<&'a mut R> {
318 while let Some(table) = self.stack.pop() {
319 self.stack.extend(table.next.values_mut());
320 if let Some(wildcard) = table.wildcard.as_mut() {
321 self.stack.push(&mut wildcard.table);
322 }
323 if let Some(res) = &mut table.accept {
324 return Some(res);
325 }
326 }
327 None
328 }
329}
330
331#[cfg(test)]
332mod test {
333 use std::collections::HashSet;
334
335 use super::*;
336
337 #[test]
338 fn empty_route_no_matches() {
339 let table: PathTable<()> = PathTable::new();
340
341 assert!(table.route("").is_none());
342 assert!(table.route("/").is_none());
343 assert!(table.route("//").is_none());
344 assert!(table.route("foo").is_none());
345 assert!(table.route("foo/bar").is_none());
346 }
347
348 #[test]
349 fn root_matches() {
350 let mut table: PathTable<()> = PathTable::new();
351 table.setup("/");
352
353 assert!(table.route("").is_some());
354 assert!(table.route("/").is_some());
355 assert!(table.route("//").is_some());
356
357 assert!(table.route("foo").is_none());
358 assert!(table.route("foo/bar").is_none());
359 }
360
361 #[test]
362 fn one_fixed_segment_matches() {
363 let mut table: PathTable<()> = PathTable::new();
364 table.setup("foo");
365
366 assert!(table.route("").is_none());
367 assert!(table.route("/").is_none());
368 assert!(table.route("//").is_none());
369
370 assert!(table.route("foo").is_some());
371 assert!(table.route("/foo").is_some());
372 assert!(table.route("foo/").is_some());
373 assert!(table.route("/foo/").is_some());
374 assert!(table.route("//foo//").is_some());
375
376 assert!(table.route("foo/bar").is_none());
377 assert!(table.route("fo/o").is_none());
378 }
379
380 #[test]
381 fn multiple_fixed_segment_matches() {
382 let mut table: PathTable<()> = PathTable::new();
383 table.setup("foo");
384 table.setup("bar");
385
386 assert!(table.route("").is_none());
387 assert!(table.route("/").is_none());
388 assert!(table.route("//").is_none());
389
390 assert!(table.route("foo").is_some());
391 assert!(table.route("bar").is_some());
392
393 assert!(table.route("foo/bar").is_none());
394 assert!(table.route("bar/foo").is_none())
395 }
396
397 #[test]
398 fn nested_fixed_segment_matches() {
399 let mut table: PathTable<()> = PathTable::new();
400 table.setup("foo/bar");
401
402 assert!(table.route("").is_none());
403 assert!(table.route("foo").is_none());
404
405 assert!(table.route("foo/bar").is_some());
406 }
407
408 #[test]
409 fn multiple_nested_fixed_segment_matches() {
410 let mut table: PathTable<()> = PathTable::new();
411 table.setup("foo/bar");
412 table.setup("baz");
413 table.setup("quux/twiddle/twibble");
414
415 assert!(table.route("").is_none());
416 assert!(table.route("foo").is_none());
417 assert!(table.route("quux").is_none());
418
419 assert!(table.route("foo/bar").is_some());
420 assert!(table.route("baz").is_some());
421 assert!(table.route("quux/twiddle/twibble").is_some());
422 }
423
424 #[test]
425 fn overlap_nested_fixed_segment_matches() {
426 let mut table: PathTable<i32> = PathTable::new();
427 *table.setup("") = 0;
428 *table.setup("foo") = 1;
429 *table.setup("foo/bar") = 2;
430
431 assert_eq!(*table.route("/").unwrap().0, 0);
432 assert_eq!(*table.route("/foo").unwrap().0, 1);
433 assert_eq!(*table.route("/foo/bar").unwrap().0, 2);
434
435 assert_eq!(*table.route("").unwrap().0, 0);
436 assert_eq!(*table.route("foo").unwrap().0, 1);
437 assert_eq!(*table.route("foo/bar").unwrap().0, 2);
438 }
439
440 #[test]
441 fn wildcard_matches() {
442 let mut table: PathTable<()> = PathTable::new();
443 table.setup("{}");
444
445 assert!(table.route("").is_none());
446 assert!(table.route("foo/bar").is_none());
447
448 assert!(table.route("foo").is_some());
449 assert!(table.route("bar").is_some());
450 }
451
452 #[test]
453 fn nested_wildcard_matches() {
454 let mut table: PathTable<()> = PathTable::new();
455 table.setup("{}/{}");
456
457 assert!(table.route("").is_none());
458 assert!(table.route("foo").is_none());
459
460 assert!(table.route("foo/bar").is_some());
461 assert_eq!(&table.route("foo/bar").unwrap().1.vec, &["foo", "bar"]);
462 assert!(table.route("foo/bar").unwrap().1.map.is_empty());
463 }
464
465 #[test]
466 fn mixed_route() {
467 let mut table: PathTable<()> = PathTable::new();
468 table.setup("foo/{}/bar");
469
470 assert!(table.route("").is_none());
471 assert!(table.route("foo").is_none());
472 assert!(table.route("foo/bar").is_none());
473 assert!(table.route("foo/bar/baz").is_none());
474
475 assert!(table.route("foo/baz/bar").is_some());
476 assert_eq!(&table.route("foo/baz/bar").unwrap().1.vec, &["baz"]);
477 }
478
479 #[test]
480 fn wildcard_fallback() {
481 let mut table: PathTable<i32> = PathTable::new();
482 *table.setup("foo") = 0;
483 *table.setup("foo/bar") = 1;
484 *table.setup("foo/{}/bar") = 2;
485
486 assert!(table.route("").is_none());
487 assert!(table.route("foo/bar/baz").is_none());
488 assert!(table.route("foo/bar/bar").is_none());
489
490 assert_eq!(*table.route("foo").unwrap().0, 0);
491 assert_eq!(*table.route("foo/bar").unwrap().0, 1);
492 assert_eq!(*table.route("foo/baz/bar").unwrap().0, 2);
493 }
494
495 #[test]
496 fn named_wildcard() {
497 let mut table: PathTable<()> = PathTable::new();
498 *table.setup("foo/{foo}");
499 *table.setup("foo/{foo}/{bar}");
500 *table.setup("{}");
501
502 let (_, params) = table.route("foo/a").unwrap();
503 assert_eq!(params.vec, &["a"]);
504 assert_eq!(params.map, [("foo", "a")].iter().cloned().collect());
505
506 let (_, params) = table.route("foo/a/b").unwrap();
507 assert_eq!(params.vec, &["a", "b"]);
508 assert_eq!(
509 params.map,
510 [("foo", "a"), ("bar", "b")].iter().cloned().collect()
511 );
512
513 let (_, params) = table.route("c").unwrap();
514 assert_eq!(params.vec, &["c"]);
515 assert!(params.map.is_empty());
516 }
517
518 #[test]
519 fn wildcard_count_mod() {
520 let mut table: PathTable<usize> = PathTable::new();
521 *table.setup("foo/{foo}*") = 0;
522 *table.setup("bar/{}*") = 1;
523 *table.setup("baz/*") = 2;
524 *table.setup("foo/bar") = 3;
525
526 let (&id, params) = table.route("foo/a/b").unwrap();
527 assert_eq!(id, 0);
528 assert_eq!(params.vec, &["a/b"]);
529 assert_eq!(params.map, [("foo", "a/b")].iter().cloned().collect());
530
531 let (&id, params) = table.route("bar/a/b").unwrap();
532 assert_eq!(id, 1);
533 assert_eq!(params.vec, &["a/b"]);
534 assert!(params.map.is_empty());
535
536 let (&id, params) = table.route("baz/a/b").unwrap();
537 assert_eq!(id, 2);
538 assert_eq!(params.vec, &["a/b"]);
539 assert!(params.map.is_empty());
540
541 let (&id, params) = table.route("foo/bar").unwrap();
542 assert_eq!(id, 3);
543 assert!(params.vec.is_empty());
544 assert!(params.map.is_empty());
545 }
546
547 #[test]
548 #[should_panic]
549 fn conflicting_wildcard_name_fails() {
550 let mut table: PathTable<()> = PathTable::new();
551 *table.setup("foo/{foo}");
552 *table.setup("foo/{bar}");
553 }
554
555 #[test]
556 #[should_panic]
557 fn conflicting_wildcard_modifier_fails() {
558 let mut table: PathTable<()> = PathTable::new();
559 table.setup("foo/{foo}*");
560 table.setup("foo/{foo}");
561 }
562
563 #[test]
564 fn iter() {
565 let mut table: PathTable<usize> = PathTable::new();
566 *table.setup("foo") = 1;
567 *table.setup("foo/bar") = 2;
568 *table.setup("{}") = 3;
569
570 let set: HashSet<_> = table.iter().cloned().collect();
571 assert_eq!(set, [1, 2, 3].iter().cloned().collect());
572 }
573
574 #[test]
575 fn iter_mut() {
576 let mut table: PathTable<usize> = PathTable::new();
577 *table.setup("foo") = 1;
578 *table.setup("foo/bar") = 2;
579 *table.setup("{}") = 3;
580
581 for res in table.iter_mut() {
582 *res -= 1;
583 }
584
585 let set: HashSet<_> = table.iter().cloned().collect();
586 assert_eq!(set, [0, 1, 2].iter().cloned().collect());
587 }
588}