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