via_router/path/
pattern.rs

1use std::fmt::{self, Debug, Display};
2
3#[derive(PartialEq)]
4pub enum Pattern {
5    Root,
6    Static(Param),
7    Dynamic(Param),
8    Wildcard(Param),
9}
10
11/// An identifier for a named path segment.
12///
13#[derive(Debug, PartialEq)]
14pub struct Param {
15    ident: Box<str>,
16}
17
18/// Unwraps the remaining slice of a path segment after the first char or
19/// panics with a custom message.
20///
21macro_rules! rest_or {
22    (
23        // Should evaluate to a `&str`.
24        $segment:expr,
25        // Args passed to panic! if the segment is empty.
26        $($arg:tt)*
27    ) => {{
28        let segment = $segment;
29        match segment.get(1..) {
30            // The range is out of bounds or produced an empty str.
31            None | Some("") => panic!($($arg)*),
32            // The range is valid.
33            Some(rest) => rest,
34        }
35    }};
36}
37
38/// Returns an iterator that yields a `Pattern` for each segment in the uri path.
39///
40pub fn patterns(path: &'static str) -> impl Iterator<Item = Pattern> {
41    super::split(path).into_iter().map(|at| {
42        let end = at.end();
43        let start = at.start();
44        let segment = match path.get(start..end) {
45            Some(slice) => slice,
46            None => panic!("Path segments cannot be empty when defining a route."),
47        };
48
49        match segment.chars().next() {
50            // Path segments that start with a colon are considered a Dynamic
51            // pattern. The remaining characters in the segment are considered
52            // the name or identifier associated with the parameter.
53            Some(':') => {
54                let rest = rest_or!(segment, "Dynamic parameters must be named. Found ':'.");
55                let param = Param::new(rest);
56
57                Pattern::Dynamic(param)
58            }
59
60            // Path segments that start with an asterisk are considered CatchAll
61            // pattern. The remaining characters in the segment are considered
62            // the name or identifier associated with the parameter.
63            Some('*') => {
64                let rest = rest_or!(segment, "Wildcard parameters must be named. Found '*'.");
65                let param = Param::new(rest);
66
67                Pattern::Wildcard(param)
68            }
69
70            // The segment does not start with a reserved character. We will
71            // consider it a static pattern that can be matched by value.
72            _ => {
73                let param = Param::new(segment);
74                Pattern::Static(param)
75            }
76        }
77    })
78}
79
80impl Param {
81    pub fn as_str(&self) -> &str {
82        &self.ident
83    }
84}
85
86impl Param {
87    pub(crate) fn new(ident: &str) -> Self {
88        Self {
89            ident: ident.into(),
90        }
91    }
92}
93
94impl Clone for Param {
95    fn clone(&self) -> Self {
96        Self {
97            ident: self.ident.clone(),
98        }
99    }
100}
101
102impl Display for Param {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        Display::fmt(&self.ident, f)
105    }
106}