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 fn get(&self, name: &str) -> Option<&str> {
29 self.params.get(name).map(String::as_str)
30 }
31
32 fn insert(&mut self, key: String, value: String) {
33 self.params.insert(key, value);
34 }
35}
36
37enum Segment {
38 Literal(String),
39 Param(String),
40 Wildcard(String),
41}
42
43type HandlerFn =
44 Box<dyn Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static>;
45
46struct Route {
47 method: String,
48 segments: Vec<Segment>,
49 handler: HandlerFn,
50}
51
52pub struct Router {
91 routes: Vec<Route>,
92}
93
94impl Router {
95 pub fn new() -> Self {
96 Router { routes: Vec::new() }
97 }
98
99 pub fn get<F>(self, pattern: &str, handler: F) -> Self
101 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
102 self.add("GET", pattern, handler)
103 }
104
105 pub fn post<F>(self, pattern: &str, handler: F) -> Self
107 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
108 self.add("POST", pattern, handler)
109 }
110
111 pub fn put<F>(self, pattern: &str, handler: F) -> Self
113 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
114 self.add("PUT", pattern, handler)
115 }
116
117 pub fn patch<F>(self, pattern: &str, handler: F) -> Self
119 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
120 self.add("PATCH", pattern, handler)
121 }
122
123 pub fn delete<F>(self, pattern: &str, handler: F) -> Self
125 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
126 self.add("DELETE", pattern, handler)
127 }
128
129 fn add<F>(mut self, method: &str, pattern: &str, handler: F) -> Self
130 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
131 self.routes.push(Route {
132 method: method.to_string(),
133 segments: Self::parse_pattern(pattern),
134 handler: Box::new(handler),
135 });
136 self
137 }
138
139 fn parse_pattern(pattern: &str) -> Vec<Segment> {
140 if pattern == "/" {
141 return vec![];
142 }
143 pattern
144 .split('/')
145 .filter(|s| !s.is_empty())
146 .map(|seg| {
147 if let Some(name) = seg.strip_prefix(':') {
148 Segment::Param(name.to_string())
149 } else if let Some(name) = seg.strip_prefix('*') {
150 Segment::Wildcard(name.to_string())
151 } else {
152 Segment::Literal(seg.to_string())
153 }
154 })
155 .collect()
156 }
157
158 pub fn handle(&self, request: &Request, connection: &ConnectionInfo) -> Option<Response> {
163 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
164 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
165
166 for route in &self.routes {
167 if route.method != request.method {
168 continue;
169 }
170 if let Some(params) = Self::try_match(&route.segments, &path_segs) {
171 return Some((route.handler)(request, ¶ms, connection));
172 }
173 }
174 None
175 }
176
177 fn try_match(pattern: &[Segment], path: &[&str]) -> Option<PathParams> {
178 let mut params = PathParams::new();
179 let mut pi = 0;
180
181 for (si, seg) in pattern.iter().enumerate() {
182 match seg {
183 Segment::Literal(lit) => {
184 if pi >= path.len() || path[pi] != lit.as_str() {
185 return None;
186 }
187 pi += 1;
188 }
189 Segment::Param(name) => {
190 if pi >= path.len() {
191 return None;
192 }
193 params.insert(name.clone(), path[pi].to_string());
194 pi += 1;
195 }
196 Segment::Wildcard(name) => {
197 if si != pattern.len() - 1 {
198 return None; }
200 params.insert(name.clone(), path[pi..].join("/"));
201 pi = path.len();
202 }
203 }
204 }
205
206 if pi == path.len() { Some(params) } else { None }
207 }
208}