little_hyper/path/
path_regex.rs1use regex::Regex;
2use std::collections::HashMap;
3
4const DYNAMIC_PATH_STARTS_WITH: &str = ":";
5
6pub 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 regex.push_str(paths.next().unwrap_or_default());
19
20 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 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
43pub 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}