1use crate::handler::{into_boxed_handler, BoxedHandler, Handler};
4use http::{Extensions, Method};
5use matchit::Router as MatchitRouter;
6use rustapi_openapi::Operation;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10pub struct MethodRouter {
12 handlers: HashMap<Method, BoxedHandler>,
13 pub(crate) operations: HashMap<Method, Operation>,
14}
15
16impl MethodRouter {
17 pub fn new() -> Self {
19 Self {
20 handlers: HashMap::new(),
21 operations: HashMap::new(),
22 }
23 }
24
25 fn on(mut self, method: Method, handler: BoxedHandler, operation: Operation) -> Self {
27 self.handlers.insert(method.clone(), handler);
28 self.operations.insert(method, operation);
29 self
30 }
31
32 pub(crate) fn get_handler(&self, method: &Method) -> Option<&BoxedHandler> {
34 self.handlers.get(method)
35 }
36
37 pub(crate) fn allowed_methods(&self) -> Vec<Method> {
39 self.handlers.keys().cloned().collect()
40 }
41
42 pub(crate) fn from_boxed(handlers: HashMap<Method, BoxedHandler>) -> Self {
44 Self {
45 handlers,
46 operations: HashMap::new(), }
48 }
49}
50
51impl Default for MethodRouter {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57pub fn get<H, T>(handler: H) -> MethodRouter
59where
60 H: Handler<T>,
61 T: 'static,
62{
63 let mut op = Operation::new();
64 H::update_operation(&mut op);
65 MethodRouter::new().on(Method::GET, into_boxed_handler(handler), op)
66}
67
68pub fn post<H, T>(handler: H) -> MethodRouter
70where
71 H: Handler<T>,
72 T: 'static,
73{
74 let mut op = Operation::new();
75 H::update_operation(&mut op);
76 MethodRouter::new().on(Method::POST, into_boxed_handler(handler), op)
77}
78
79pub fn put<H, T>(handler: H) -> MethodRouter
81where
82 H: Handler<T>,
83 T: 'static,
84{
85 let mut op = Operation::new();
86 H::update_operation(&mut op);
87 MethodRouter::new().on(Method::PUT, into_boxed_handler(handler), op)
88}
89
90pub fn patch<H, T>(handler: H) -> MethodRouter
92where
93 H: Handler<T>,
94 T: 'static,
95{
96 let mut op = Operation::new();
97 H::update_operation(&mut op);
98 MethodRouter::new().on(Method::PATCH, into_boxed_handler(handler), op)
99}
100
101pub fn delete<H, T>(handler: H) -> MethodRouter
103where
104 H: Handler<T>,
105 T: 'static,
106{
107 let mut op = Operation::new();
108 H::update_operation(&mut op);
109 MethodRouter::new().on(Method::DELETE, into_boxed_handler(handler), op)
110}
111
112pub struct Router {
114 inner: MatchitRouter<MethodRouter>,
115 state: Arc<Extensions>,
116}
117
118impl Router {
119 pub fn new() -> Self {
121 Self {
122 inner: MatchitRouter::new(),
123 state: Arc::new(Extensions::new()),
124 }
125 }
126
127 pub fn route(mut self, path: &str, method_router: MethodRouter) -> Self {
129 let matchit_path = convert_path_params(path);
131
132 match self.inner.insert(matchit_path.clone(), method_router) {
133 Ok(_) => {}
134 Err(e) => {
135 panic!("Route conflict: {} - {}", path, e);
136 }
137 }
138 self
139 }
140
141 pub fn state<S: Clone + Send + Sync + 'static>(mut self, state: S) -> Self {
143 let extensions = Arc::make_mut(&mut self.state);
144 extensions.insert(state);
145 self
146 }
147
148 pub fn nest(self, _prefix: &str, _router: Router) -> Self {
150 self
152 }
153
154 pub(crate) fn match_route(
156 &self,
157 path: &str,
158 method: &Method,
159 ) -> RouteMatch<'_> {
160 match self.inner.at(path) {
161 Ok(matched) => {
162 let method_router = matched.value;
163
164 if let Some(handler) = method_router.get_handler(method) {
165 let params: HashMap<String, String> = matched
167 .params
168 .iter()
169 .map(|(k, v)| (k.to_string(), v.to_string()))
170 .collect();
171
172 RouteMatch::Found { handler, params }
173 } else {
174 RouteMatch::MethodNotAllowed {
175 allowed: method_router.allowed_methods(),
176 }
177 }
178 }
179 Err(_) => RouteMatch::NotFound,
180 }
181 }
182
183 pub(crate) fn state_ref(&self) -> Arc<Extensions> {
185 self.state.clone()
186 }
187}
188
189impl Default for Router {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195pub(crate) enum RouteMatch<'a> {
197 Found {
198 handler: &'a BoxedHandler,
199 params: HashMap<String, String>,
200 },
201 NotFound,
202 MethodNotAllowed {
203 allowed: Vec<Method>,
204 },
205}
206
207fn convert_path_params(path: &str) -> String {
209 let mut result = String::with_capacity(path.len());
210
211 for ch in path.chars() {
212 match ch {
213 '{' => {
214 result.push(':');
215 }
216 '}' => {
217 }
219 _ => {
220 result.push(ch);
221 }
222 }
223 }
224
225 result
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_convert_path_params() {
234 assert_eq!(convert_path_params("/users/{id}"), "/users/:id");
235 assert_eq!(
236 convert_path_params("/users/{user_id}/posts/{post_id}"),
237 "/users/:user_id/posts/:post_id"
238 );
239 assert_eq!(convert_path_params("/static/path"), "/static/path");
240 }
241}