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}