1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use lazy_static::lazy_static;
use phf::{phf_map, Map};
use regex::Regex;
use std::borrow::Cow;

lazy_static! {
    static ref SEGMENT_MATCH: Regex = Regex::new(r"^\{(?::(?P<kind>[a-zA-Z]\w*))?\}$").unwrap();
}

static MATCH_KINDS: Map<&'static str, &'static str> = phf_map! {
    "string" => r"([^/]+)",
    "int" => r"([-+]?\d+)",
    "uint" => r"(\d+)",
    "uuid" => r"([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
};

#[derive(Debug, Clone)]
/// A single route in the router.  This contains information about the path;
/// specifically, the path itself, the method, the handler, and how to match
/// it.
pub struct Route<M, H> {
    pub(super) path: Cow<'static, str>,
    pub(super) method: M,
    pub(super) handler: H,
    pub(super) pattern: Regex,
}

impl<M, H> Route<M, H> {
    /// Creates a new route with the given information.  The path is parsed,
    /// and the new route is returned.
    pub fn new<P>(path: P, method: M, handler: H) -> Route<M, H>
    where
        P: Into<Cow<'static, str>>,
    {
        let path = path.into();
        let compile = parse(path.as_ref());
        Route {
            path,
            method,
            handler,
            pattern: compile,
        }
    }
}

fn parse(path: &str) -> Regex {
    let normalized = crate::normalize_url(path);
    let split = normalized.split("/").skip(1);
    let mut pattern = split
        .map(|part| {
            if let Some(cap) = SEGMENT_MATCH.captures(part) {
                let name = cap.name("kind").map(|m| m.as_str()).unwrap_or("string");
                Cow::Borrowed(MATCH_KINDS.get(name).map(|v| *v).unwrap_or(r"([^/]*)"))
            } else {
                Cow::Owned(regex::escape(part))
            }
        })
        .fold(String::from("^"), |mut acc, el| {
            acc.push('/');
            acc.push_str(el.as_ref());
            acc
        });

    pattern.push('$');
    Regex::new(&pattern).unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_route_parse() {
        fn assert_path(given: &str, expected: &str) {
            assert_eq!(parse(given).as_str(), expected)
        }
        assert_path("/some/path", r"^/some/path$");
        assert_path("/some/{:string}", r"^/some/([^/]+)$");
        assert_path("/some/{:int}", r"^/some/([-+]?\d+)$");
        assert_path("/some/{:uint}", r"^/some/(\d+)$");
        assert_path(
            "/some/{:uuid}",
            r"^/some/([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$",
        );
    }
}