little_hyper/path/
path_regex.rs

1use regex::Regex;
2use std::collections::HashMap;
3
4const DYNAMIC_PATH_STARTS_WITH: &str = ":";
5
6/// Path To Regex
7///
8///
9///
10/// TODO: include ':' means, dynamic
11/// TODO: implement '$' starts with
12pub fn path_to_regex(sanitized_path: &str) -> String {
13    let mut regex = String::new();
14
15    let mut paths = sanitized_path.split(DYNAMIC_PATH_STARTS_WITH);
16
17    // 1st item
18    regex.push_str(paths.next().unwrap_or_default());
19
20    // rest items
21    paths.for_each(|path_item| {
22        if let Some((name, rest)) = path_item.split_once('/') {
23            regex.push_str(&naming_regex_maker(name));
24            regex.push('/');
25            regex.push_str(rest);
26        } else {
27            regex.push_str(&naming_regex_maker(path_item));
28        }
29    });
30
31    // end the regex
32    regex.push('$');
33
34    regex
35}
36
37fn naming_regex_maker(name: &str) -> String {
38    format!(r"(?<{}>\w+)", name)
39}
40
41pub type Params = HashMap<String, String>;
42
43/// Path Regex Matcher
44///
45/// - `target_pathname` must be provided from `request.pathname`.
46pub fn path_regex_matcher<'a>(regex_path: &str, target_pathname: &'a str) -> Option<Params> {
47    let regex_path = Regex::new(regex_path).expect("ERROR: Regex::new()");
48
49    if let Some(captures) = regex_path.captures(target_pathname) {
50        let params = regex_path
51            .capture_names()
52            .into_iter()
53            .filter_map(|name| name)
54            .fold(Params::new(), |mut acc, name| {
55                let value = captures.name(name).map_or("", |i| i.as_str());
56
57                acc.insert(name.to_string(), value.to_string());
58
59                acc
60            });
61
62        return Some(params);
63    }
64
65    None
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_path_to_regex() {
74        assert_eq!(
75            path_to_regex("/users/:userId"),
76            r"/users/(?<userId>\w+)$",
77            "Single dynamic regex part."
78        );
79        assert_eq!(
80            path_to_regex("/users/:userId/messages"),
81            r"/users/(?<userId>\w+)/messages$"
82        );
83        assert_eq!(
84            path_to_regex("/users/:userId/:messageId"),
85            r"/users/(?<userId>\w+)/(?<messageId>\w+)$",
86            "Multiple dynamic regex parts."
87        );
88        assert_eq!(path_to_regex("/users/list"), r"/users/list$");
89        assert_eq!(path_to_regex("/"), r"/$");
90    }
91
92    #[test]
93    fn test_naming_regex_maker() {
94        assert_eq!(naming_regex_maker("userId"), r"(?<userId>\w+)");
95    }
96}