1use super::{HttpMethod, RouteRegistry, RouteInfo};
4use service_builder::builder;
5use axum::{
6 Router as AxumRouter,
7 routing::{get, post, put, delete, patch},
8 handler::Handler,
9};
10use std::sync::{Arc, Mutex};
11
12#[derive(Debug)]
14pub struct RouteGroup<S = ()>
15where
16 S: Clone + Send + Sync + 'static,
17{
18 prefix: String,
19 name: String,
20 router: AxumRouter<S>,
21 registry: Arc<Mutex<RouteRegistry>>,
22 #[allow(dead_code)]
23 middleware: Vec<String>, }
25
26impl<S> RouteGroup<S>
27where
28 S: Clone + Send + Sync + 'static,
29{
30 pub fn new(name: &str, prefix: &str, registry: Arc<Mutex<RouteRegistry>>) -> Self {
32 Self {
33 prefix: prefix.trim_end_matches('/').to_string(),
34 name: name.to_string(),
35 router: AxumRouter::new(),
36 registry,
37 middleware: Vec::new(),
38 }
39 }
40
41 fn full_path(&self, path: &str) -> String {
43 let path = path.trim_start_matches('/');
44 if self.prefix.is_empty() {
45 format!("/{}", path)
46 } else {
47 format!("{}/{}", self.prefix, path)
48 }
49 }
50
51 fn register_route(&self, method: HttpMethod, path: &str, route_name: Option<String>) {
53 let full_path = self.full_path(path);
54 let params = self.extract_param_names(&full_path);
55
56 let final_name = route_name.or_else(|| {
57 let path_segments: Vec<&str> = path.trim_matches('/')
59 .split('/')
60 .filter(|s| !s.is_empty() && !s.starts_with('{'))
61 .collect();
62
63 if path_segments.is_empty() {
64 Some(format!("{}.{}", self.name, method_to_string(&method).to_lowercase()))
65 } else {
66 Some(format!("{}.{}.{}",
67 self.name,
68 method_to_string(&method).to_lowercase(),
69 path_segments.join("_")
70 ))
71 }
72 });
73
74 let route_info = RouteInfo {
75 name: final_name,
76 path: full_path,
77 method,
78 params,
79 group: Some(self.name.clone()),
80 };
81
82 let route_id = format!("{}_{}", self.name, uuid::Uuid::new_v4());
83 self.registry.lock().unwrap().register(route_id, route_info);
84 }
85
86 fn extract_param_names(&self, path: &str) -> Vec<String> {
88 path.split('/')
89 .filter_map(|segment| {
90 if segment.starts_with('{') && segment.ends_with('}') {
91 Some(segment[1..segment.len()-1].to_string())
92 } else {
93 None
94 }
95 })
96 .collect()
97 }
98
99 pub fn get<H, T>(mut self, path: &str, handler: H) -> Self
101 where
102 H: Handler<T, S>,
103 T: 'static,
104 {
105 self.register_route(HttpMethod::GET, path, None);
106 self.router = self.router.route(path, get(handler));
107 self
108 }
109
110 pub fn post<H, T>(mut self, path: &str, handler: H) -> Self
112 where
113 H: Handler<T, S>,
114 T: 'static,
115 {
116 self.register_route(HttpMethod::POST, path, None);
117 self.router = self.router.route(path, post(handler));
118 self
119 }
120
121 pub fn put<H, T>(mut self, path: &str, handler: H) -> Self
123 where
124 H: Handler<T, S>,
125 T: 'static,
126 {
127 self.register_route(HttpMethod::PUT, path, None);
128 self.router = self.router.route(path, put(handler));
129 self
130 }
131
132 pub fn delete<H, T>(mut self, path: &str, handler: H) -> Self
134 where
135 H: Handler<T, S>,
136 T: 'static,
137 {
138 self.register_route(HttpMethod::DELETE, path, None);
139 self.router = self.router.route(path, delete(handler));
140 self
141 }
142
143 pub fn patch<H, T>(mut self, path: &str, handler: H) -> Self
145 where
146 H: Handler<T, S>,
147 T: 'static,
148 {
149 self.register_route(HttpMethod::PATCH, path, None);
150 self.router = self.router.route(path, patch(handler));
151 self
152 }
153
154 pub fn route<H, T>(mut self, method: HttpMethod, path: &str, name: &str, handler: H) -> Self
156 where
157 H: Handler<T, S>,
158 T: 'static,
159 {
160 self.register_route(method.clone(), path, Some(name.to_string()));
161
162 let axum_method = axum::http::Method::from(method);
163 match axum_method {
164 axum::http::Method::GET => self.router = self.router.route(path, get(handler)),
165 axum::http::Method::POST => self.router = self.router.route(path, post(handler)),
166 axum::http::Method::PUT => self.router = self.router.route(path, put(handler)),
167 axum::http::Method::DELETE => self.router = self.router.route(path, delete(handler)),
168 axum::http::Method::PATCH => self.router = self.router.route(path, patch(handler)),
169 _ => {} }
171
172 self
173 }
174
175 pub fn prefix(&self) -> &str {
177 &self.prefix
178 }
179
180 pub fn name(&self) -> &str {
182 &self.name
183 }
184
185 pub fn into_router(self) -> AxumRouter<S> {
187 self.router
188 }
189}
190
191#[derive(Debug, Clone)]
193#[builder]
194pub struct GroupBuilderConfig {
195 #[builder(getter)]
196 pub name: String,
197
198 #[builder(default, getter)]
199 pub prefix: String,
200
201 #[builder(default, getter)]
202 pub middleware: Vec<String>,
203}
204
205impl GroupBuilderConfig {
206 pub fn build_group<S>(self, registry: Arc<Mutex<RouteRegistry>>) -> RouteGroup<S>
208 where
209 S: Clone + Send + Sync + 'static,
210 {
211 RouteGroup::new(&self.name, &self.prefix, registry)
212 }
213}
214
215impl GroupBuilderConfigBuilder {
217 pub fn add_middleware(self, middleware_name: &str) -> Self {
219 let mut middlewares_vec = self.middleware.unwrap_or_default();
220 middlewares_vec.push(middleware_name.to_string());
221 GroupBuilderConfigBuilder {
222 name: self.name,
223 prefix: self.prefix,
224 middleware: Some(middlewares_vec),
225 }
226 }
227
228 pub fn add_middlewares(self, new_middlewares: Vec<String>) -> Self {
230 let mut middlewares_vec = self.middleware.unwrap_or_default();
231 middlewares_vec.extend(new_middlewares);
232 GroupBuilderConfigBuilder {
233 name: self.name,
234 prefix: self.prefix,
235 middleware: Some(middlewares_vec),
236 }
237 }
238
239 pub fn build_config(self) -> GroupBuilderConfig {
240 self.build_with_defaults().unwrap()
241 }
242}
243
244pub struct GroupBuilder {
246 builder_config: GroupBuilderConfigBuilder,
247}
248
249impl GroupBuilder {
250 pub fn new(name: &str) -> Self {
251 Self {
252 builder_config: GroupBuilderConfig::builder().name(name.to_string()),
253 }
254 }
255
256 pub fn prefix(self, prefix: &str) -> Self {
258 Self {
259 builder_config: self.builder_config.prefix(prefix.to_string()),
260 }
261 }
262
263 pub fn middleware(self, middleware_name: &str) -> Self {
265 Self {
266 builder_config: self.builder_config.add_middleware(middleware_name),
267 }
268 }
269
270 pub fn middlewares(self, middlewares: Vec<String>) -> Self {
272 Self {
273 builder_config: self.builder_config.add_middlewares(middlewares),
274 }
275 }
276
277 pub fn build<S>(self, registry: Arc<Mutex<RouteRegistry>>) -> RouteGroup<S>
279 where
280 S: Clone + Send + Sync + 'static,
281 {
282 self.builder_config.build_config().build_group(registry)
283 }
284}
285
286fn method_to_string(method: &HttpMethod) -> &'static str {
288 match method {
289 HttpMethod::GET => "GET",
290 HttpMethod::POST => "POST",
291 HttpMethod::PUT => "PUT",
292 HttpMethod::DELETE => "DELETE",
293 HttpMethod::PATCH => "PATCH",
294 HttpMethod::HEAD => "HEAD",
295 HttpMethod::OPTIONS => "OPTIONS",
296 HttpMethod::TRACE => "TRACE",
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use axum::response::Html;
304
305 async fn handler() -> Html<&'static str> {
306 Html("<h1>Handler</h1>")
307 }
308
309 #[test]
310 fn test_group_creation() {
311 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
312 let group = RouteGroup::<()>::new("api", "/api/v1", Arc::clone(®istry));
313
314 assert_eq!(group.name(), "api");
315 assert_eq!(group.prefix(), "/api/v1");
316 }
317
318 #[test]
319 fn test_group_path_generation() {
320 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
321 let group = RouteGroup::<()>::new("api", "/api/v1", registry);
322
323 assert_eq!(group.full_path("users"), "/api/v1/users");
324 assert_eq!(group.full_path("/users/{id}"), "/api/v1/users/{id}");
325 }
326
327 #[test]
328 fn test_group_builder() {
329 let registry = Arc::new(Mutex::new(RouteRegistry::new()));
330 let group = GroupBuilder::new("api")
331 .prefix("/api/v1")
332 .middleware("auth")
333 .build::<()>(registry)
334 .get("/users", handler)
335 .post("/users", handler);
336
337 assert_eq!(group.name(), "api");
338 assert_eq!(group.prefix(), "/api/v1");
339 }
340}