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