1use crate::dep::DepEnv;
7use crate::error::{Error, Result};
8use crate::handler::{BoxHandlerFn, Handler};
9use crate::middleware::Middleware;
10use http::Method;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14pub struct MethodRouter {
16 pub(crate) handlers: Vec<(Method, BoxHandlerFn)>,
17 pub(crate) body_limit: Option<usize>,
20 pub(crate) stream_body: bool,
23}
24
25pub fn get<H: Handler<A>, A>(h: H) -> MethodRouter {
26 MethodRouter::new().on(Method::GET, h)
27}
28pub fn post<H: Handler<A>, A>(h: H) -> MethodRouter {
29 MethodRouter::new().on(Method::POST, h)
30}
31pub fn put<H: Handler<A>, A>(h: H) -> MethodRouter {
32 MethodRouter::new().on(Method::PUT, h)
33}
34pub fn patch<H: Handler<A>, A>(h: H) -> MethodRouter {
35 MethodRouter::new().on(Method::PATCH, h)
36}
37pub fn delete<H: Handler<A>, A>(h: H) -> MethodRouter {
38 MethodRouter::new().on(Method::DELETE, h)
39}
40
41impl MethodRouter {
42 fn new() -> Self {
43 Self {
44 handlers: Vec::new(),
45 body_limit: None,
46 stream_body: false,
47 }
48 }
49
50 pub fn on<H: Handler<A>, A>(mut self, method: Method, h: H) -> Self {
51 self.handlers.push((method, h.into_handler_fn()));
52 self
53 }
54
55 pub fn body_limit(mut self, bytes: usize) -> Self {
60 self.body_limit = Some(bytes);
61 self
62 }
63
64 pub fn stream_body(mut self) -> Self {
68 self.stream_body = true;
69 self
70 }
71 pub fn get<H: Handler<A>, A>(self, h: H) -> Self {
72 self.on(Method::GET, h)
73 }
74 pub fn post<H: Handler<A>, A>(self, h: H) -> Self {
75 self.on(Method::POST, h)
76 }
77 pub fn put<H: Handler<A>, A>(self, h: H) -> Self {
78 self.on(Method::PUT, h)
79 }
80 pub fn patch<H: Handler<A>, A>(self, h: H) -> Self {
81 self.on(Method::PATCH, h)
82 }
83 pub fn delete<H: Handler<A>, A>(self, h: H) -> Self {
84 self.on(Method::DELETE, h)
85 }
86}
87
88pub(crate) struct Endpoint {
91 pub(crate) methods: HashMap<Method, BoxHandlerFn>,
92 pub(crate) env: Arc<DepEnv>,
93 pub(crate) middleware: Arc<[Arc<dyn Middleware>]>,
94 pub(crate) body_limit: Option<usize>,
97 pub(crate) stream_body: bool,
100}
101
102#[derive(Default)]
103pub(crate) struct Trie {
104 root: Node,
105}
106
107#[derive(Default)]
108struct Node {
109 statics: HashMap<String, Node>,
110 param: Option<(String, Box<Node>)>,
111 endpoint: Option<Endpoint>,
112}
113
114pub(crate) enum RouteMatch<'a> {
115 Found {
116 endpoint: &'a Endpoint,
117 params: Vec<(String, String)>,
118 },
119 MethodMissing,
120 Malformed,
121 NotFound,
122}
123
124fn segments(path: &str) -> impl Iterator<Item = &str> {
125 path.split('/').filter(|s| !s.is_empty())
126}
127
128fn decode_segment(seg: &str) -> Option<String> {
132 if !seg.contains('%') {
133 return Some(seg.to_string());
134 }
135 fn hex(b: u8) -> Option<u8> {
136 match b {
137 b'0'..=b'9' => Some(b - b'0'),
138 b'a'..=b'f' => Some(b - b'a' + 10),
139 b'A'..=b'F' => Some(b - b'A' + 10),
140 _ => None,
141 }
142 }
143 let bytes = seg.as_bytes();
144 let mut out = Vec::with_capacity(bytes.len());
145 let mut i = 0;
146 while i < bytes.len() {
147 if bytes[i] == b'%' {
148 let high = hex(*bytes.get(i + 1)?)?;
150 let low = hex(*bytes.get(i + 2)?)?;
151 out.push(high * 16 + low);
152 i += 3;
153 } else {
154 out.push(bytes[i]);
155 i += 1;
156 }
157 }
158 String::from_utf8(out).ok()
159}
160
161impl Trie {
162 pub(crate) fn insert(&mut self, path: &str, endpoint: Endpoint) -> Result<()> {
167 let mut node = &mut self.root;
168 for seg in segments(path) {
169 if let Some(name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
170 if node.param.is_none() {
171 node.param = Some((name.to_string(), Box::default()));
172 }
173 let (existing, child) = node.param.as_mut().expect("just ensured");
174 if existing != name {
175 return Err(Error::internal(format!(
176 "conflicting path parameters `{{{existing}}}` vs `{{{name}}}` in `{path}`"
177 )));
178 }
179 node = child;
180 } else {
181 node = node.statics.entry(seg.to_string()).or_default();
182 }
183 }
184 if node.endpoint.is_some() {
185 return Err(Error::internal(format!(
186 "duplicate route registration for `{path}`"
187 )));
188 }
189 node.endpoint = Some(endpoint);
190 Ok(())
191 }
192
193 pub(crate) fn find<'a>(&'a self, path: &str, method: &Method) -> RouteMatch<'a> {
194 if !path.contains('%') {
195 let segs: Vec<&str> = segments(path).collect();
196 return self.find_in(&segs, method);
197 }
198 let mut decoded: Vec<String> = Vec::new();
199 for raw in segments(path) {
200 match decode_segment(raw) {
201 Some(d) => decoded.push(d),
202 None => return RouteMatch::Malformed,
203 }
204 }
205 let segs: Vec<&str> = decoded.iter().map(String::as_str).collect();
206 self.find_in(&segs, method)
207 }
208
209 pub(crate) fn methods_for(&self, path: &str) -> Option<Vec<Method>> {
216 let mut params: Vec<(String, String)> = Vec::new();
217 let node = if path.contains('%') {
218 let mut decoded: Vec<String> = Vec::new();
219 for raw in segments(path) {
220 decoded.push(decode_segment(raw)?);
221 }
222 let segs: Vec<&str> = decoded.iter().map(String::as_str).collect();
223 find_node(&self.root, &segs, &mut params)
224 } else {
225 let segs: Vec<&str> = segments(path).collect();
226 find_node(&self.root, &segs, &mut params)
227 }?;
228 let ep = node
229 .endpoint
230 .as_ref()
231 .expect("find_node only returns endpoint nodes");
232 let mut methods: Vec<Method> = ep.methods.keys().cloned().collect();
233 methods.sort_by(|a, b| a.as_str().cmp(b.as_str()));
234 Some(methods)
235 }
236
237 fn find_in<'a>(&'a self, segs: &[&str], method: &Method) -> RouteMatch<'a> {
238 let mut params: Vec<(String, String)> = Vec::new();
239 match find_node(&self.root, segs, &mut params) {
240 Some(node) => {
241 let ep = node
242 .endpoint
243 .as_ref()
244 .expect("find_node only returns endpoint nodes");
245 if ep.methods.contains_key(method) {
246 RouteMatch::Found {
247 endpoint: ep,
248 params,
249 }
250 } else {
251 RouteMatch::MethodMissing
252 }
253 }
254 None => RouteMatch::NotFound,
255 }
256 }
257}
258
259fn find_node<'a>(
263 node: &'a Node,
264 segs: &[&str],
265 params: &mut Vec<(String, String)>,
266) -> Option<&'a Node> {
267 let Some((head, rest)) = segs.split_first() else {
268 return node.endpoint.is_some().then_some(node);
269 };
270 if let Some(child) = node.statics.get(*head)
271 && let Some(found) = find_node(child, rest, params)
272 {
273 return Some(found);
274 }
275 if let Some((name, child)) = &node.param {
276 params.push((name.clone(), (*head).to_string()));
277 if let Some(found) = find_node(child, rest, params) {
278 return Some(found);
279 }
280 params.pop();
281 }
282 None
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::response::IntoResponse;
289
290 fn dummy_handler() -> BoxHandlerFn {
291 Arc::new(move |_ctx: &mut crate::RequestCtx| Box::pin(async move { "ok".into_response() }))
292 }
293
294 fn endpoint(methods: &[Method]) -> Endpoint {
295 let mut map = HashMap::new();
296 for m in methods {
297 map.insert(m.clone(), dummy_handler());
298 }
299 Endpoint {
300 methods: map,
301 env: Arc::new(DepEnv::default()),
302 middleware: Arc::from(vec![]),
303 body_limit: None,
304 stream_body: false,
305 }
306 }
307
308 #[test]
309 fn static_and_param_segments_match() {
310 let mut t = Trie::default();
311 t.insert("/todos", endpoint(&[Method::GET])).unwrap();
312 t.insert("/todos/{id}", endpoint(&[Method::GET, Method::DELETE]))
313 .unwrap();
314 t.insert("/todos/{id}/comments", endpoint(&[Method::GET]))
315 .unwrap();
316
317 match t.find("/todos/42/comments", &Method::GET) {
318 RouteMatch::Found { params, .. } => {
319 assert_eq!(params, vec![("id".to_string(), "42".to_string())])
320 }
321 _ => panic!("expected match"),
322 }
323 assert!(matches!(
324 t.find("/todos/42", &Method::DELETE),
325 RouteMatch::Found { .. }
326 ));
327 }
328
329 #[test]
330 fn unknown_path_is_not_found_and_wrong_method_is_method_missing() {
331 let mut t = Trie::default();
332 t.insert("/todos", endpoint(&[Method::GET])).unwrap();
333 assert!(matches!(
334 t.find("/nope", &Method::GET),
335 RouteMatch::NotFound
336 ));
337 assert!(matches!(
338 t.find("/todos", &Method::POST),
339 RouteMatch::MethodMissing
340 ));
341 }
342
343 #[test]
344 fn duplicate_path_registration_is_a_build_error() {
345 let mut t = Trie::default();
346 t.insert("/todos", endpoint(&[Method::GET])).unwrap();
347 let err = t.insert("/todos", endpoint(&[Method::POST])).unwrap_err();
348 assert!(err.message().contains("/todos"));
349 }
350
351 #[test]
352 fn conflicting_param_names_are_a_build_error() {
353 let mut t = Trie::default();
354 t.insert("/todos/{id}", endpoint(&[Method::GET])).unwrap();
355 let err = t
356 .insert("/todos/{todo_id}", endpoint(&[Method::DELETE]))
357 .unwrap_err();
358 assert!(err.message().contains("id"));
359 }
360
361 #[test]
362 fn static_dead_end_backtracks_to_param_branch() {
363 let mut t = Trie::default();
364 t.insert("/a/b/c", endpoint(&[Method::GET])).unwrap();
365 t.insert("/a/{x}/d", endpoint(&[Method::GET])).unwrap();
366 match t.find("/a/b/d", &Method::GET) {
367 RouteMatch::Found { params, .. } => {
368 assert_eq!(params, vec![("x".to_string(), "b".to_string())]);
369 }
370 _ => panic!("expected /a/{{x}}/d to match /a/b/d via backtracking"),
371 }
372 assert!(matches!(
373 t.find("/a/b/c", &Method::GET),
374 RouteMatch::Found { .. }
375 ));
376 }
377
378 #[test]
379 fn static_wins_over_param_when_both_match() {
380 let mut t = Trie::default();
381 t.insert("/users/me", endpoint(&[Method::GET])).unwrap();
382 t.insert("/users/{id}", endpoint(&[Method::GET])).unwrap();
383 match t.find("/users/me", &Method::GET) {
384 RouteMatch::Found { params, .. } => {
385 assert!(params.is_empty(), "static match captures nothing")
386 }
387 _ => panic!("expected static /users/me"),
388 }
389 match t.find("/users/42", &Method::GET) {
390 RouteMatch::Found { params, .. } => {
391 assert_eq!(params, vec![("id".to_string(), "42".to_string())])
392 }
393 _ => panic!("expected param /users/{{id}}"),
394 }
395 }
396
397 #[test]
398 fn method_router_builder_collects_methods() {
399 let mr = get(|| async { "a" }).post(|| async { "b" });
400 let methods: Vec<_> = mr.handlers.iter().map(|(m, _)| m.clone()).collect();
401 assert_eq!(methods, vec![Method::GET, Method::POST]);
402 }
403
404 #[test]
405 fn percent_encoded_segments_decode_for_statics_and_params() {
406 let mut t = Trie::default();
407 t.insert("/caf\u{e9}/menu", endpoint(&[Method::GET]))
408 .unwrap();
409 t.insert("/todos/{id}", endpoint(&[Method::GET])).unwrap();
410
411 assert!(matches!(
413 t.find("/caf%C3%A9/menu", &Method::GET),
414 RouteMatch::Found { .. }
415 ));
416
417 match t.find("/todos/a%2Fb", &Method::GET) {
419 RouteMatch::Found { params, .. } => assert_eq!(params[0].1, "a/b"),
420 other => panic!(
421 "expected param capture, got no match ({})",
422 matches!(other, RouteMatch::NotFound)
423 ),
424 }
425
426 match t.find("/todos/hello%20world", &Method::GET) {
428 RouteMatch::Found { params, .. } => assert_eq!(params[0].1, "hello world"),
429 _ => panic!("expected match"),
430 }
431 }
432
433 #[test]
434 fn malformed_percent_encodings_are_flagged_not_matched() {
435 let mut t = Trie::default();
436 t.insert("/todos/{id}", endpoint(&[Method::GET])).unwrap();
437 assert!(matches!(
438 t.find("/todos/%zz", &Method::GET),
439 RouteMatch::Malformed
440 ));
441 assert!(matches!(
442 t.find("/todos/%2", &Method::GET),
443 RouteMatch::Malformed
444 )); assert!(matches!(
446 t.find("/todos/%FF", &Method::GET),
447 RouteMatch::Malformed
448 )); }
450}