router_matcher/
lib.rs

1use std::collections::HashMap;
2
3pub struct RouteMatcher {
4    root: TrieNode,
5}
6
7struct TrieNode {
8    segment: Segment,
9    children: Vec<TrieNode>,
10    path: Option<String>,
11}
12
13#[derive(Clone, PartialEq, Eq)]
14enum Segment {
15    Static(String),
16    Parameter(String),
17    Wildcard,
18}
19
20pub struct MatchedRoute {
21    pub path: String,
22    pub parameters: HashMap<String, String>,
23    pub url_parameters: HashMap<String, String>,
24}
25
26impl RouteMatcher {
27    pub fn new() -> RouteMatcher {
28        RouteMatcher {
29            root: TrieNode::new_root(),
30        }
31    }
32
33    pub fn add_route(&mut self, path: &str) {
34        let segments = path
35            .split('/')
36            .filter(|s| !s.is_empty())
37            .map(|s| {
38                if s.starts_with(':') {
39                    Segment::Parameter(s[1..].to_string())
40                } else if s == "*" {
41                    Segment::Wildcard
42                } else {
43                    Segment::Static(s.to_string())
44                }
45            })
46            .collect::<Vec<_>>();
47        self.root.add_route(path, &segments);
48    }
49
50    pub fn match_route(&self, url: &str) -> Option<MatchedRoute> {
51        let (path, query_string) = url.split_at(url.find('?').unwrap_or_else(|| url.len()));
52        let segments = path
53            .split('/')
54            .filter(|s| !s.is_empty())
55            .collect::<Vec<_>>();
56
57        if let Some((path, parameters)) = self.root.match_segments(&segments, 0) {
58            let url_parameters = query_string
59                .trim_start_matches('?')
60                .split('&')
61                .filter(|s| !s.is_empty())
62                .map(|s| {
63                    let mut parts = s.split('=');
64                    (
65                        parts.next().unwrap().to_string(),
66                        parts.next().unwrap_or("").to_string(),
67                    )
68                })
69                .collect::<HashMap<_, _>>();
70            Some(MatchedRoute {
71                path,
72                parameters,
73                url_parameters,
74            })
75        } else {
76            None
77        }
78    }
79}
80
81impl TrieNode {
82    fn new_root() -> TrieNode {
83        TrieNode {
84            segment: Segment::Static("".to_string()),
85            children: Vec::new(),
86            path: None,
87        }
88    }
89
90    fn add_route(&mut self, path: &str, segments: &[Segment]) {
91        if segments.is_empty() {
92            self.path = Some(path.to_string());
93            return;
94        }
95
96        for child in &mut self.children {
97            if child.segment == segments[0] {
98                child.add_route(path, &segments[1..]);
99                return;
100            }
101        }
102
103        let mut new_node = TrieNode {
104            segment: segments[0].clone(),
105            children: Vec::new(),
106            path: None,
107        };
108        new_node.add_route(path, &segments[1..]);
109        self.children.push(new_node);
110    }
111
112    fn match_segments(
113        &self,
114        segments: &[&str],
115        start: usize,
116    ) -> Option<(String, HashMap<String, String>)> {
117        if start == segments.len() {
118            return self.path.clone().map(|path| (path, HashMap::new()));
119        }
120
121        for child in &self.children {
122            match &child.segment {
123                Segment::Static(s) => {
124                    if s == segments[start] {
125                        if let Some(result) = child.match_segments(segments, start + 1) {
126                            return Some(result);
127                        }
128                    }
129                }
130                Segment::Parameter(param) => {
131                    let mut parameters = HashMap::new();
132                    parameters.insert(param.clone(), segments[start].to_string());
133                    if let Some((path, child_params)) = child.match_segments(segments, start + 1) {
134                        parameters.extend(child_params);
135                        return Some((path, parameters));
136                    }
137                }
138                Segment::Wildcard => {
139                    if let Some((path, params)) = child.match_segments(segments, segments.len()) {
140                        return Some((path, params));
141                    }
142                }
143            }
144        }
145
146        None
147    }
148}