rust_web_server/router/
mod.rs1#[cfg(test)]
2mod tests;
3
4use std::collections::HashMap;
5
6use crate::request::Request;
7use crate::response::Response;
8use crate::server::ConnectionInfo;
9
10pub struct PathParams {
19 params: HashMap<String, String>,
20}
21
22impl PathParams {
23 fn new() -> Self {
24 PathParams { params: HashMap::new() }
25 }
26
27 pub(crate) fn from_map(params: HashMap<String, String>) -> Self {
29 PathParams { params }
30 }
31
32 pub fn get(&self, name: &str) -> Option<&str> {
34 self.params.get(name).map(String::as_str)
35 }
36
37 fn insert(&mut self, key: String, value: String) {
38 self.params.insert(key, value);
39 }
40}
41
42enum Segment {
43 Literal(String),
44 Param(String),
45 Wildcard(String),
46}
47
48type HandlerFn =
49 Box<dyn Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static>;
50
51struct Route {
52 method: String,
53 segments: Vec<Segment>,
54 handler: HandlerFn,
55}
56
57pub struct Router {
96 routes: Vec<Route>,
97}
98
99impl Router {
100 pub fn new() -> Self {
101 Router { routes: Vec::new() }
102 }
103
104 pub fn get<F>(self, pattern: &str, handler: F) -> Self
106 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
107 self.add("GET", pattern, handler)
108 }
109
110 pub fn post<F>(self, pattern: &str, handler: F) -> Self
112 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
113 self.add("POST", pattern, handler)
114 }
115
116 pub fn put<F>(self, pattern: &str, handler: F) -> Self
118 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
119 self.add("PUT", pattern, handler)
120 }
121
122 pub fn patch<F>(self, pattern: &str, handler: F) -> Self
124 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
125 self.add("PATCH", pattern, handler)
126 }
127
128 pub fn delete<F>(self, pattern: &str, handler: F) -> Self
130 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
131 self.add("DELETE", pattern, handler)
132 }
133
134 fn add<F>(mut self, method: &str, pattern: &str, handler: F) -> Self
135 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
136 self.routes.push(Route {
137 method: method.to_string(),
138 segments: Self::parse_pattern(pattern),
139 handler: Box::new(handler),
140 });
141 self
142 }
143
144 fn parse_pattern(pattern: &str) -> Vec<Segment> {
145 if pattern == "/" {
146 return vec![];
147 }
148 pattern
149 .split('/')
150 .filter(|s| !s.is_empty())
151 .map(|seg| {
152 if let Some(name) = seg.strip_prefix(':') {
153 Segment::Param(name.to_string())
154 } else if let Some(name) = seg.strip_prefix('*') {
155 Segment::Wildcard(name.to_string())
156 } else {
157 Segment::Literal(seg.to_string())
158 }
159 })
160 .collect()
161 }
162
163 pub fn handle(&self, request: &Request, connection: &ConnectionInfo) -> Option<Response> {
168 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
169 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
170
171 for route in &self.routes {
172 if route.method != request.method {
173 continue;
174 }
175 if let Some(params) = Self::try_match(&route.segments, &path_segs) {
176 return Some((route.handler)(request, ¶ms, connection));
177 }
178 }
179 None
180 }
181
182 fn try_match(pattern: &[Segment], path: &[&str]) -> Option<PathParams> {
183 let mut params = PathParams::new();
184 let mut pi = 0;
185
186 for (si, seg) in pattern.iter().enumerate() {
187 match seg {
188 Segment::Literal(lit) => {
189 if pi >= path.len() || path[pi] != lit.as_str() {
190 return None;
191 }
192 pi += 1;
193 }
194 Segment::Param(name) => {
195 if pi >= path.len() {
196 return None;
197 }
198 params.insert(name.clone(), path[pi].to_string());
199 pi += 1;
200 }
201 Segment::Wildcard(name) => {
202 if si != pattern.len() - 1 {
203 return None; }
205 params.insert(name.clone(), path[pi..].join("/"));
206 pi = path.len();
207 }
208 }
209 }
210
211 if pi == path.len() { Some(params) } else { None }
212 }
213}