1use super::{HttpMethod, RouteRegistry, RouteInfo};
4use axum::{
5 Router as AxumRouter,
6 routing::{get, post, put, delete, patch},
7 handler::Handler,
8};
9use std::sync::{Arc, Mutex};
10
11#[derive(Debug)]
13pub struct RouteGroup<S = ()>
14where
15 S: Clone + Send + Sync + 'static,
16{
17 prefix: String,
18 name: String,
19 router: AxumRouter<S>,
20 registry: Arc<Mutex<RouteRegistry>>,
21 middleware: Vec<String>, }
23
24impl<S> RouteGroup<S>
25where
26 S: Clone + Send + Sync + 'static,
27{
28 pub fn new(name: &str, prefix: &str, registry: Arc<Mutex<RouteRegistry>>) -> Self {
30 Self {
31 prefix: prefix.trim_end_matches('/').to_string(),
32 name: name.to_string(),
33 router: AxumRouter::new(),
34 registry,
35 middleware: Vec::new(),
36 }
37 }
38
39 fn full_path(&self, path: &str) -> String {
41 let path = path.trim_start_matches('/');
42 if self.prefix.is_empty() {
43 format!("/{}", path)
44 } else {
45 format!("{}/{}", self.prefix, path)
46 }
47 }
48
49 fn register_route(&self, method: HttpMethod, path: &str, route_name: Option<String>) {
51 let full_path = self.full_path(path);
52 let params = self.extract_param_names(&full_path);
53
54 let final_name = route_name.or_else(|| {
55 let path_segments: Vec<&str> = path.trim_matches('/')
57 .split('/')
58 .filter(|s| !s.is_empty() && !s.starts_with('{'))
59 .collect();
60
61 if path_segments.is_empty() {
62 Some(format!("{}.{}", self.name, method_to_string(&method).to_lowercase()))
63 } else {
64 Some(format!("{}.{}.{}",
65 self.name,
66 method_to_string(&method).to_lowercase(),
67 path_segments.join("_")
68 ))
69 }
70 });
71
72 let route_info = RouteInfo {
73 name: final_name,
74 path: full_path,
75 method,
76 params,
77 group: Some(self.name.clone()),
78 };
79
80 let route_id = format!("{}_{}", self.name, uuid::Uuid::new_v4());
81 self.registry.lock().unwrap().register(route_id, route_info);
82 }
83
84 fn extract_param_names(&self, path: &str) -> Vec<String> {
86 path.split('/')
87 .filter_map(|segment| {
88 if segment.starts_with('{') && segment.ends_with('}') {
89 Some(segment[1..segment.len()-1].to_string())
90 } else {
91 None
92 }
93 })
94 .collect()
95 }
96
97 pub fn get<H, T>(mut self, path: &str, handler: H) -> Self
99 where
100 H: Handler<T, S>,
101 T: 'static,
102 {
103 self.register_route(HttpMethod::GET, path, None);
104 self.router = self.router.route(path, get(handler));
105 self
106 }
107
108 pub fn post<H, T>(mut self, path: &str, handler: H) -> Self
110 where
111 H: Handler<T, S>,
112 T: 'static,
113 {
114 self.register_route(HttpMethod::POST, path, None);
115 self.router = self.router.route(path, post(handler));
116 self
117 }
118
119 pub fn put<H, T>(mut self, path: &str, handler: H) -> Self
121 where
122 H: Handler<T, S>,
123 T: 'static,
124 {
125 self.register_route(HttpMethod::PUT, path, None);
126 self.router = self.router.route(path, put(handler));
127 self
128 }
129
130 pub fn delete<H, T>(mut self, path: &str, handler: H) -> Self
132 where
133 H: Handler<T, S>,
134 T: 'static,
135 {
136 self.register_route(HttpMethod::DELETE, path, None);
137 self.router = self.router.route(path, delete(handler));
138 self
139 }
140
141 pub fn patch<H, T>(mut self, path: &str, handler: H) -> Self
143 where
144 H: Handler<T, S>,
145 T: 'static,
146 {
147 self.register_route(HttpMethod::PATCH, path, None);
148 self.router = self.router.route(path, patch(handler));
149 self
150 }
151
152 pub fn route<H, T>(mut self, method: HttpMethod, path: &str, name: &str, handler: H) -> Self
154 where
155 H: Handler<T, S>,
156 T: 'static,
157 {
158 self.register_route(method.clone(), path, Some(name.to_string()));
159
160 let axum_method = axum::http::Method::from(method);
161 match axum_method {
162 axum::http::Method::GET => self.router = self.router.route(path, get(handler)),
163 axum::http::Method::POST => self.router = self.router.route(path, post(handler)),
164 axum::http::Method::PUT => self.router = self.router.route(path, put(handler)),
165 axum::http::Method::DELETE => self.router = self.router.route(path, delete(handler)),
166 axum::http::Method::PATCH => self.router = self.router.route(path, patch(handler)),
167 _ => {} }
169
170 self
171 }
172
173 pub fn prefix(&self) -> &str {
175 &self.prefix
176 }
177
178 pub fn name(&self) -> &str {
180 &self.name
181 }
182
183 pub fn into_router(self) -> AxumRouter<S> {
185 self.router
186 }
187}
188
189#[derive(Debug)]
191pub struct GroupBuilder {
192 name: String,
193 prefix: String,
194 middleware: Vec<String>,
195}
196
197impl GroupBuilder {
198 pub fn new(name: &str) -> Self {
199 Self {
200 name: name.to_string(),
201 prefix: String::new(),
202 middleware: Vec::new(),
203 }
204 }
205
206 pub fn prefix(mut self, prefix: &str) -> Self {
208 self.prefix = prefix.to_string();
209 self
210 }
211
212 pub fn middleware(mut self, middleware_name: &str) -> Self {
214 self.middleware.push(middleware_name.to_string());
215 self
216 }
217
218 pub fn build<S>(self, registry: Arc<Mutex<RouteRegistry>>) -> RouteGroup<S>
220 where
221 S: Clone + Send + Sync + 'static,
222 {
223 RouteGroup::new(&self.name, &self.prefix, registry)
224 }
225}
226
227fn method_to_string(method: &HttpMethod) -> &'static str {
229 match method {
230 HttpMethod::GET => "GET",
231 HttpMethod::POST => "POST",
232 HttpMethod::PUT => "PUT",
233 HttpMethod::DELETE => "DELETE",
234 HttpMethod::PATCH => "PATCH",
235 HttpMethod::HEAD => "HEAD",
236 HttpMethod::OPTIONS => "OPTIONS",
237 HttpMethod::TRACE => "TRACE",
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use axum::response::Html;
245
246 async fn handler() -> Html<&'static str> {
247 Html("<h1>Handler</h1>")
248 }
249
250 #[test]
251 fn test_group_creation() {
252 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
253 let group = RouteGroup::<()>::new("api", "/api/v1", Arc::clone(®istry));
254
255 assert_eq!(group.name(), "api");
256 assert_eq!(group.prefix(), "/api/v1");
257 }
258
259 #[test]
260 fn test_group_path_generation() {
261 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
262 let group = RouteGroup::<()>::new("api", "/api/v1", registry);
263
264 assert_eq!(group.full_path("users"), "/api/v1/users");
265 assert_eq!(group.full_path("/users/{id}"), "/api/v1/users/{id}");
266 }
267
268 #[test]
269 fn test_group_builder() {
270 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
271 let group = GroupBuilder::new("api")
272 .prefix("/api/v1")
273 .middleware("auth")
274 .build::<()>(registry)
275 .get("/users", handler)
276 .post("/users", handler);
277
278 assert_eq!(group.name(), "api");
279 assert_eq!(group.prefix(), "/api/v1");
280 }
281}