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