micro_web/router/
mod.rs

1pub mod filter;
2
3use crate::PathParams;
4use crate::handler::RequestHandler;
5
6use crate::handler::handler_decorator::HandlerDecorator;
7use crate::handler::handler_decorator_factory::{
8    HandlerDecoratorFactory, HandlerDecoratorFactoryComposer, HandlerDecoratorFactoryExt, IdentityHandlerDecoratorFactory,
9};
10use filter::{AllFilter, Filter};
11use std::collections::HashMap;
12use tracing::error;
13
14type RouterFilter = dyn Filter + Send + Sync + 'static;
15type InnerRouter<T> = matchit::Router<T>;
16
17/// Main router structure that handles HTTP request routing
18pub struct Router {
19    inner_router: InnerRouter<Vec<RouterItem>>,
20}
21
22/// A router item containing a filter and handler
23pub struct RouterItem {
24    filter: Box<RouterFilter>,
25    handler: Box<dyn RequestHandler>,
26}
27
28/// Result of matching a route, containing matched items and path parameters
29pub struct RouteResult<'router, 'req> {
30    router_items: &'router [RouterItem],
31    params: PathParams<'router, 'req>,
32}
33
34impl Router {
35    /// Creates a new router builder with default wrappers
36    pub fn builder() -> RouterBuilder<IdentityHandlerDecoratorFactory> {
37        RouterBuilder::new()
38    }
39
40    /// Matches a path against the router's routes
41    ///
42    /// Returns a `RouteResult` containing matched handlers and path parameters
43    ///
44    /// # Arguments
45    /// * `path` - The path to match against
46    pub fn at<'router, 'req>(&'router self, path: &'req str) -> RouteResult<'router, 'req> {
47        self.inner_router
48            .at(path)
49            .map(|matched| RouteResult { router_items: matched.value.as_slice(), params: matched.params.into() })
50            .map_err(|e| error!("match '{}' error: {}", path, e))
51            .unwrap_or(RouteResult::empty())
52    }
53}
54
55impl RouterItem {
56    /// Gets the filter for this router item
57    pub fn filter(&self) -> &RouterFilter {
58        self.filter.as_ref()
59    }
60
61    /// Gets the request handler for this router item
62    pub fn handler(&self) -> &dyn RequestHandler {
63        self.handler.as_ref()
64    }
65}
66
67impl<'router, 'req> RouteResult<'router, 'req> {
68    fn empty() -> Self {
69        Self { router_items: &[], params: PathParams::empty() }
70    }
71
72    /// Returns true if no routes were matched
73    #[inline]
74    pub fn is_empty(&self) -> bool {
75        self.router_items.is_empty()
76    }
77
78    /// Gets the path parameters from the matched route
79    pub fn params(&self) -> &PathParams<'router, 'req> {
80        &self.params
81    }
82
83    /// Gets the matched router items
84    pub fn router_items(&self) -> &'router [RouterItem] {
85        self.router_items
86    }
87}
88
89pub struct RouterBuilder<DF> {
90    data: HashMap<String, Vec<RouterItemBuilder>>,
91    decorator_factory: DF,
92}
93
94impl RouterBuilder<IdentityHandlerDecoratorFactory> {
95    fn new() -> Self {
96        Self { data: HashMap::new(), decorator_factory: IdentityHandlerDecoratorFactory }
97    }
98}
99impl<DF> RouterBuilder<DF> {
100    pub fn route(mut self, route: impl Into<String>, item_builder: RouterItemBuilder) -> Self {
101        let vec = self.data.entry(route.into()).or_default();
102        vec.push(item_builder);
103        self
104    }
105
106    pub fn with_global_decorator<DF2>(self, factory: DF2) -> RouterBuilder<HandlerDecoratorFactoryComposer<DF, DF2>>
107    where
108        DF: HandlerDecoratorFactory,
109        DF2: HandlerDecoratorFactory,
110    {
111        RouterBuilder { data: self.data, decorator_factory: self.decorator_factory.and_then(factory) }
112    }
113
114    /// Builds the router from the accumulated routes and wrappers
115    pub fn build(self) -> Router
116    where
117        DF: HandlerDecoratorFactory,
118    {
119        let mut inner_router = InnerRouter::new();
120
121        for (path, items) in self.data.into_iter() {
122            let router_items = items
123                .into_iter()
124                .map(|item_builder| item_builder.build())
125                .map(|item| {
126                    let decorator = self.decorator_factory.create_decorator();
127                    let handler = decorator.decorate(item.handler);
128                    RouterItem { handler: Box::new(handler), ..item }
129                })
130                .collect::<Vec<_>>();
131
132            inner_router.insert(path, router_items).unwrap();
133        }
134
135        Router { inner_router }
136    }
137}
138
139macro_rules! method_router_filter {
140    ($method:ident, $method_name:ident) => {
141        pub fn $method<H: RequestHandler + 'static>(handler: H) -> RouterItemBuilder {
142            let mut filters = filter::all_filter();
143            filters.and(filter::$method_name());
144            RouterItemBuilder { filters, handler: Box::new(handler) }
145        }
146    };
147}
148
149method_router_filter!(get, get_method);
150method_router_filter!(post, post_method);
151method_router_filter!(put, put_method);
152method_router_filter!(delete, delete_method);
153method_router_filter!(head, head_method);
154method_router_filter!(options, options_method);
155method_router_filter!(connect, connect_method);
156method_router_filter!(patch, patch_method);
157method_router_filter!(trace, trace_method);
158
159pub struct RouterItemBuilder {
160    filters: AllFilter,
161    handler: Box<dyn RequestHandler>,
162}
163
164impl RouterItemBuilder {
165    pub fn with<F: Filter + Send + Sync + 'static>(mut self, filter: F) -> Self {
166        self.filters.and(filter);
167        self
168    }
169
170    fn build(self) -> RouterItem {
171        // todo: we can remove indirect when filters has only one filter
172        RouterItem { filter: Box::new(self.filters), handler: self.handler }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::filter::header;
179    use super::{Router, get, post};
180    use crate::{PathParams, RequestContext, handler_fn};
181    use http::{HeaderValue, Method, Request};
182    use micro_http::protocol::RequestHeader;
183
184    async fn simple_get_1(_method: &Method) -> String {
185        "hello world".into()
186    }
187
188    async fn simple_get_2(_method: &Method) -> String {
189        "hello world".into()
190    }
191
192    fn router() -> Router {
193        Router::builder()
194            .route("/", get(handler_fn(simple_get_1)))
195            .route(
196                "/",
197                post(handler_fn(simple_get_1)).with(header(
198                    http::header::CONTENT_TYPE,
199                    HeaderValue::from_str(mime::APPLICATION_WWW_FORM_URLENCODED.as_ref()).unwrap(),
200                )),
201            )
202            .route("/", post(handler_fn(simple_get_1)))
203            .route("/2", get(handler_fn(simple_get_2)))
204            .build()
205    }
206
207    #[test]
208    fn test_route_get() {
209        let router = router();
210        let route_result = router.at("/");
211
212        assert_eq!(route_result.params.len(), 0);
213
214        let items = route_result.router_items;
215        assert_eq!(items.len(), 3);
216
217        let header: RequestHeader = Request::builder().method(Method::GET).body(()).unwrap().into_parts().0.into();
218        let params = PathParams::empty();
219        let req_ctx = RequestContext::new(&header, &params);
220
221        assert!(items[0].filter.matches(&req_ctx));
222        assert!(!items[1].filter.matches(&req_ctx));
223        assert!(!items[2].filter.matches(&req_ctx));
224    }
225
226    #[test]
227    fn test_route_post() {
228        let router = router();
229        let route_result = router.at("/");
230
231        assert_eq!(route_result.params.len(), 0);
232
233        let items = route_result.router_items;
234        assert_eq!(items.len(), 3);
235
236        let header: RequestHeader = Request::builder().method(Method::POST).body(()).unwrap().into_parts().0.into();
237        let params = PathParams::empty();
238        let req_ctx = RequestContext::new(&header, &params);
239
240        assert!(!items[0].filter.matches(&req_ctx));
241        assert!(!items[1].filter.matches(&req_ctx));
242        assert!(items[2].filter.matches(&req_ctx));
243    }
244
245    #[test]
246    fn test_route_post_with_content_type() {
247        let router = router();
248        let route_result = router.at("/");
249
250        assert_eq!(route_result.params.len(), 0);
251
252        let items = route_result.router_items;
253        assert_eq!(items.len(), 3);
254
255        let header: RequestHeader = Request::builder()
256            .method(Method::POST)
257            .header(http::header::CONTENT_TYPE, "application/x-www-form-urlencoded")
258            .body(())
259            .unwrap()
260            .into_parts()
261            .0
262            .into();
263        let params = PathParams::empty();
264        let req_ctx = RequestContext::new(&header, &params);
265
266        assert!(!items[0].filter.matches(&req_ctx));
267        assert!(items[1].filter.matches(&req_ctx));
268        assert!(items[2].filter.matches(&req_ctx));
269    }
270}