1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::fmt::Debug;
4
5#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq)]
7pub enum RouteType {
8 Static,
10 Dynamic,
12 Wildcard,
14}
15
16pub enum RouteMatch<T: Clone> {
18 Matched {
19 handler: T,
20 params: HashMap<String, String>,
21 },
22 NotMatched,
23}
24
25pub struct Route<T: Clone> {
27 path: String,
28 parts: Vec<String>,
29 handler: T,
30 route_type: RouteType,
31}
32
33pub struct Router<T: Clone> {
35 routes: Vec<Route<T>>,
36}
37
38impl<T: Clone> Router<T> {
39 pub fn new() -> Self {
40 Router { routes: vec![] }
41 }
42
43 pub fn add(&mut self, path: impl Into<String>, handler: T) {
49 let path = path.into();
50
51 let route_type = if path.contains("*") {
52 RouteType::Wildcard
53 } else if path.contains(":") {
54 RouteType::Dynamic
55 } else {
56 RouteType::Static
57 };
58
59 let parts = path
60 .trim_matches('/')
61 .split('/')
62 .map(|s| s.to_string())
63 .collect();
64
65 self.routes.push(Route {
66 path,
67 parts,
68 handler,
69 route_type,
70 });
71
72 self.routes.sort_by(|a, b| a.route_type.cmp(&b.route_type))
74 }
75
76 pub fn remove(&mut self, path: &str) -> Option<Route<T>> {
78 for i in (0..self.routes.len()).rev() {
79 if path.cmp(&self.routes[i].path) == Ordering::Equal {
80 return Some(self.routes.remove(i));
81 }
82 }
83
84 None
85 }
86
87 pub fn go_match(&self, path: &str) -> RouteMatch<T> {
93 let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
94
95 for route in &self.routes {
96 let route_parts: Vec<&str> = route.parts.iter().map(|s| s as &str).collect();
97 let mut params = HashMap::new();
98
99 if route_parts.len() != parts.len()
100 && !route_parts.iter().any(|part| part.starts_with('*'))
101 {
102 continue;
103 }
104
105 let mut matched = true;
106
107 for (i, route_part) in route_parts.iter().enumerate() {
108 if *route_part == "*" || route_part.starts_with('*') {
109 let key = route_part.trim_start_matches('*');
111 params.insert(key.to_string(), parts[i..].join("/"));
112 break;
113 } else if route_part.starts_with(':') {
114 let key = route_part.trim_start_matches(':');
116 if let Some(val) = parts.get(i) {
117 params.insert(key.to_string(), val.to_string());
118 } else {
119 matched = false;
120 break;
121 }
122 } else if let Some(req_part) = parts.get(i) {
123 if route_part != req_part {
124 matched = false;
125 break;
126 }
127 } else {
128 matched = false;
129 break;
130 }
131 }
132
133 if matched {
134 return RouteMatch::Matched {
135 handler: route.handler.clone(),
136 params,
137 };
138 }
139 }
140
141 RouteMatch::NotMatched
142 }
143}