Skip to main content

ullun/
router.rs

1//! Router implementation using matchit
2
3use crate::error::{Error, Result};
4use crate::handler::DynHandler;
5use crate::openapi::{RouteInfo, RouteKind};
6use crate::request::Params;
7
8/// HTTP method routing
9#[derive(Default)]
10pub struct Router {
11    get: matchit::Router<DynHandler>,
12    post: matchit::Router<DynHandler>,
13    put: matchit::Router<DynHandler>,
14    delete: matchit::Router<DynHandler>,
15    patch: matchit::Router<DynHandler>,
16    registered: Vec<RouteInfo>,
17}
18
19impl Router {
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    pub fn insert(&mut self, method: &str, path: &str, handler: DynHandler) -> Result<()> {
25        self.insert_as(RouteKind::Http, method, path, handler)
26    }
27
28    pub(crate) fn insert_as(
29        &mut self,
30        kind: RouteKind,
31        method: &str,
32        path: &str,
33        handler: DynHandler,
34    ) -> Result<()> {
35        let method = method.to_uppercase();
36        let router = match method.as_str() {
37            "GET" => &mut self.get,
38            "POST" => &mut self.post,
39            "PUT" => &mut self.put,
40            "DELETE" => &mut self.delete,
41            "PATCH" => &mut self.patch,
42            _ => {
43                return Err(Error::bad_request(format!(
44                    "Unsupported method: {}",
45                    method
46                )))
47            }
48        };
49
50        router
51            .insert(path, handler)
52            .map_err(|e| Error::internal(format!("Failed to insert route: {}", e)))?;
53
54        self.registered.push(RouteInfo {
55            method,
56            path: path.to_string(),
57            kind,
58        });
59        Ok(())
60    }
61
62    /// Methods with a route for `path`, in a fixed order, including the
63    /// implicit `HEAD` (next to `GET`) and `OPTIONS`. Empty when the path is unknown.
64    pub fn allowed_methods(&self, path: &str) -> Vec<&'static str> {
65        let tables: [(&'static str, &matchit::Router<DynHandler>); 5] = [
66            ("GET", &self.get),
67            ("POST", &self.post),
68            ("PUT", &self.put),
69            ("DELETE", &self.delete),
70            ("PATCH", &self.patch),
71        ];
72        let mut allowed = Vec::new();
73        for (name, table) in tables {
74            if table.at(path).is_ok() {
75                allowed.push(name);
76                if name == "GET" {
77                    allowed.push("HEAD");
78                }
79            }
80        }
81        if !allowed.is_empty() {
82            allowed.push("OPTIONS");
83        }
84        allowed
85    }
86
87    pub(crate) fn registered(&self) -> &[RouteInfo] {
88        &self.registered
89    }
90
91    pub fn match_route(&self, method: &str, path: &str) -> Result<(DynHandler, Params)> {
92        let router = match method.to_uppercase().as_str() {
93            // HEAD is answered by the GET handler, the body is dropped afterwards
94            "GET" | "HEAD" => &self.get,
95            "POST" => &self.post,
96            "PUT" => &self.put,
97            "DELETE" => &self.delete,
98            "PATCH" => &self.patch,
99            _ => return Err(Error::not_found("Route not found")),
100        };
101
102        let matched = router
103            .at(path)
104            .map_err(|_| Error::not_found("Route not found"))?;
105
106        let mut params = Params::new();
107        for (key, value) in matched.params.iter() {
108            params.insert(key.to_string(), value.to_string());
109        }
110
111        Ok((matched.value.clone(), params))
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::handler::Handler;
119    use crate::request::Request;
120    use crate::response::Response;
121    use std::sync::Arc;
122
123    struct TestHandler;
124
125    #[async_trait::async_trait]
126    impl Handler for TestHandler {
127        async fn call(&self, _req: Request) -> Result<Response> {
128            Ok(Response::text("test"))
129        }
130    }
131
132    #[test]
133    fn test_router_insert_and_match() {
134        let mut router = Router::new();
135        let handler = Arc::new(TestHandler) as DynHandler;
136
137        router
138            .insert("GET", "/hello/{name}", handler.clone())
139            .unwrap();
140
141        let (_, params) = router.match_route("GET", "/hello/world").unwrap();
142        assert_eq!(params.get("name").unwrap(), "world");
143    }
144
145    #[test]
146    fn allowed_methods_lists_what_the_path_supports() {
147        let mut router = Router::new();
148        let handler = Arc::new(TestHandler) as DynHandler;
149        router
150            .insert("GET", "/users/{id}", handler.clone())
151            .unwrap();
152        router
153            .insert("DELETE", "/users/{id}", handler.clone())
154            .unwrap();
155        router.insert("POST", "/users", handler.clone()).unwrap();
156
157        assert_eq!(
158            router.allowed_methods("/users/7"),
159            ["GET", "HEAD", "DELETE", "OPTIONS"]
160        );
161        assert_eq!(router.allowed_methods("/users"), ["POST", "OPTIONS"]);
162        assert!(router.allowed_methods("/nothing").is_empty());
163        // HEAD resolves like GET
164        assert!(router.match_route("HEAD", "/users/7").is_ok());
165        assert!(router.match_route("HEAD", "/users").is_err());
166    }
167
168    #[test]
169    fn test_router_not_found() {
170        let router = Router::new();
171        assert!(router.match_route("GET", "/not-found").is_err());
172    }
173}