Skip to main content

rust_webx_host/
router.rs

1//! Matchit-based router with zero-alloc route matching.
2//!
3//! Uses `matchit::Router` (the same library as Axum) for efficient
4//! radix-tree based route matching with zero heap allocations per request.
5
6use rust_webx_core::error::Result;
7use rust_webx_core::http::IHttpContext;
8use rust_webx_core::routing::{HttpMethod, IEndpoint, IRouter};
9use std::collections::HashMap;
10use std::sync::Arc;
11
12type RouteValue = (Arc<dyn IEndpoint>, String);
13
14/// Per-method matchit router.
15struct MethodRouter {
16    inner: matchit::Router<RouteValue>,
17}
18
19impl MethodRouter {
20    fn new() -> Self {
21        Self {
22            inner: matchit::Router::new(),
23        }
24    }
25
26    fn insert(&mut self, path: &str, endpoint: Arc<dyn IEndpoint>) {
27        let value = (endpoint, path.to_string());
28        if let Err(e) = self.inner.insert(path, value) {
29            tracing::warn!("[Router] Failed to register route '{}': {}", path, e);
30        }
31    }
32
33    fn at<'a>(&'a self, path: &'a str) -> Option<matchit::Match<'a, 'a, &'a RouteValue>> {
34        self.inner.at(path).ok()
35    }
36}
37
38/// Matchit-based router.
39pub struct Router {
40    get: MethodRouter,
41    post: MethodRouter,
42    put: MethodRouter,
43    delete: MethodRouter,
44    patch: MethodRouter,
45    head: MethodRouter,
46    options: MethodRouter,
47}
48
49impl Router {
50    pub fn new() -> Self {
51        Self {
52            get: MethodRouter::new(),
53            post: MethodRouter::new(),
54            put: MethodRouter::new(),
55            delete: MethodRouter::new(),
56            patch: MethodRouter::new(),
57            head: MethodRouter::new(),
58            options: MethodRouter::new(),
59        }
60    }
61
62    fn method_router_mut(&mut self, m: HttpMethod) -> &mut MethodRouter {
63        match m {
64            HttpMethod::Get => &mut self.get,
65            HttpMethod::Post => &mut self.post,
66            HttpMethod::Put => &mut self.put,
67            HttpMethod::Delete => &mut self.delete,
68            HttpMethod::Patch => &mut self.patch,
69            HttpMethod::Head => &mut self.head,
70            HttpMethod::Options => &mut self.options,
71        }
72    }
73
74    fn method_router(&self, m: HttpMethod) -> &MethodRouter {
75        match m {
76            HttpMethod::Get => &self.get,
77            HttpMethod::Post => &self.post,
78            HttpMethod::Put => &self.put,
79            HttpMethod::Delete => &self.delete,
80            HttpMethod::Patch => &self.patch,
81            HttpMethod::Head => &self.head,
82            HttpMethod::Options => &self.options,
83        }
84    }
85
86    /// Check if a path is registered for any HTTP method.
87    pub fn path_exists(&self, path: &str) -> bool {
88        self.get.at(path).is_some()
89            || self.post.at(path).is_some()
90            || self.put.at(path).is_some()
91            || self.delete.at(path).is_some()
92            || self.patch.at(path).is_some()
93            || self.head.at(path).is_some()
94            || self.options.at(path).is_some()
95    }
96}
97
98#[async_trait::async_trait]
99impl IRouter for Router {
100    fn register(&mut self, method: HttpMethod, path: &str, endpoint: Arc<dyn IEndpoint>) {
101        self.method_router_mut(method).insert(path, endpoint);
102    }
103
104    async fn match_route(
105        &self,
106        ctx: &mut dyn IHttpContext,
107    ) -> Result<Option<(Arc<dyn IEndpoint>, HashMap<String, String>, String)>> {
108        let path = ctx.request().path();
109        let method_str = ctx.request().method();
110        let method = match HttpMethod::from_str(method_str) {
111            Some(m) => m,
112            None => return Ok(None),
113        };
114
115        let router = self.method_router(method);
116
117        if let Some(matched) = router.at(path) {
118            let mut params = HashMap::new();
119            for (key, value) in matched.params.iter() {
120                params.insert(key.to_string(), value.to_string());
121            }
122
123            let (endpoint, pattern) = matched.value;
124            return Ok(Some((Arc::clone(endpoint), params, pattern.clone())));
125        }
126
127        Ok(None)
128    }
129}
130
131impl Default for Router {
132    fn default() -> Self {
133        Self::new()
134    }
135}