oxihuman_core/
route_table.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct RouteEntry {
10 pub pattern: String,
11 pub handler: String,
12 pub priority: i32,
13}
14
15#[derive(Debug, Clone)]
17pub struct RouteMatch {
18 pub handler: String,
19 pub params: Vec<(String, String)>,
20}
21
22pub struct RouteTable {
24 routes: Vec<RouteEntry>,
25 dispatch_count: u64,
26}
27
28fn match_pattern(pattern: &str, path: &str) -> Option<Vec<(String, String)>> {
29 let pat_parts: Vec<&str> = pattern.split('/').collect();
30 let path_parts: Vec<&str> = path.split('/').collect();
31 if pat_parts.len() != path_parts.len() {
32 return None;
33 }
34 let mut params = Vec::new();
35 for (pp, sp) in pat_parts.iter().zip(path_parts.iter()) {
36 if let Some(name) = pp.strip_prefix(':') {
37 params.push((name.to_string(), (*sp).to_string()));
38 } else if pp != sp {
39 return None;
40 }
41 }
42 Some(params)
43}
44
45#[allow(dead_code)]
46impl RouteTable {
47 pub fn new() -> Self {
48 RouteTable {
49 routes: Vec::new(),
50 dispatch_count: 0,
51 }
52 }
53
54 pub fn add_route(&mut self, pattern: &str, handler: &str, priority: i32) {
55 let pos = self.routes.partition_point(|r| r.priority > priority);
56 self.routes.insert(
57 pos,
58 RouteEntry {
59 pattern: pattern.to_string(),
60 handler: handler.to_string(),
61 priority,
62 },
63 );
64 }
65
66 pub fn dispatch(&mut self, path: &str) -> Option<RouteMatch> {
67 self.dispatch_count += 1;
68 for route in &self.routes {
69 if let Some(params) = match_pattern(&route.pattern, path) {
70 return Some(RouteMatch {
71 handler: route.handler.clone(),
72 params,
73 });
74 }
75 }
76 None
77 }
78
79 pub fn remove_handler(&mut self, handler: &str) -> usize {
80 let before = self.routes.len();
81 self.routes.retain(|r| r.handler != handler);
82 before - self.routes.len()
83 }
84
85 pub fn route_count(&self) -> usize {
86 self.routes.len()
87 }
88
89 pub fn dispatch_count(&self) -> u64 {
90 self.dispatch_count
91 }
92
93 pub fn has_pattern(&self, pattern: &str) -> bool {
94 self.routes.iter().any(|r| r.pattern == pattern)
95 }
96
97 pub fn handlers(&self) -> Vec<&str> {
98 self.routes.iter().map(|r| r.handler.as_str()).collect()
99 }
100
101 pub fn clear(&mut self) {
102 self.routes.clear();
103 }
104
105 pub fn is_empty(&self) -> bool {
106 self.routes.is_empty()
107 }
108}
109
110impl Default for RouteTable {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116pub fn new_route_table() -> RouteTable {
117 RouteTable::new()
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn exact_match() {
126 let mut t = new_route_table();
127 t.add_route("/health", "health_handler", 0);
128 let m = t.dispatch("/health").expect("should succeed");
129 assert_eq!(m.handler, "health_handler");
130 assert!(m.params.is_empty());
131 }
132
133 #[test]
134 fn param_extraction() {
135 let mut t = new_route_table();
136 t.add_route("/user/:id", "user_handler", 0);
137 let m = t.dispatch("/user/42").expect("should succeed");
138 assert_eq!(m.handler, "user_handler");
139 assert_eq!(m.params[0], ("id".to_string(), "42".to_string()));
140 }
141
142 #[test]
143 fn no_match() {
144 let mut t = new_route_table();
145 t.add_route("/a", "h", 0);
146 assert!(t.dispatch("/b").is_none());
147 }
148
149 #[test]
150 fn priority_ordering() {
151 let mut t = new_route_table();
152 t.add_route("/item/:id", "generic", 0);
153 t.add_route("/item/special", "specific", 10);
154 let m = t.dispatch("/item/special").expect("should succeed");
155 assert_eq!(m.handler, "specific");
156 }
157
158 #[test]
159 fn remove_handler() {
160 let mut t = new_route_table();
161 t.add_route("/a", "h", 0);
162 t.add_route("/b", "h", 0);
163 assert_eq!(t.remove_handler("h"), 2);
164 assert!(t.is_empty());
165 }
166
167 #[test]
168 fn dispatch_count_tracked() {
169 let mut t = new_route_table();
170 t.add_route("/x", "h", 0);
171 t.dispatch("/x");
172 t.dispatch("/y");
173 assert_eq!(t.dispatch_count(), 2);
174 }
175
176 #[test]
177 fn has_pattern() {
178 let mut t = new_route_table();
179 t.add_route("/foo", "h", 0);
180 assert!(t.has_pattern("/foo"));
181 assert!(!t.has_pattern("/bar"));
182 }
183
184 #[test]
185 fn multiple_params() {
186 let mut t = new_route_table();
187 t.add_route("/a/:x/b/:y", "h", 0);
188 let m = t.dispatch("/a/1/b/2").expect("should succeed");
189 assert_eq!(m.params.len(), 2);
190 }
191
192 #[test]
193 fn clear_table() {
194 let mut t = new_route_table();
195 t.add_route("/x", "h", 0);
196 t.clear();
197 assert_eq!(t.route_count(), 0);
198 }
199}