1use axum::{
2 middleware::from_fn,
3 routing::{delete, get, post, put},
4 Router,
5};
6
7use std::sync::Arc;
8
9use crate::{
10 execute_pipeline, framework_log_event, Container, ControllerBasePath, ExceptionFilter, Guard,
11 Interceptor,
12};
13
14pub struct RouteBuilder<T> {
19 router: Router<Container>,
20 _marker: std::marker::PhantomData<T>,
21}
22
23impl<T> Default for RouteBuilder<T>
24where
25 T: ControllerBasePath,
26{
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl<T> RouteBuilder<T>
33where
34 T: ControllerBasePath,
35{
36 pub fn new() -> Self {
37 Self {
38 router: Router::new(),
39 _marker: std::marker::PhantomData,
40 }
41 }
42
43 pub fn full_path(path: &str, version: Option<&str>) -> String {
44 let base = T::base_path().trim_end_matches('/');
45 let path = path.trim();
46
47 let sub = if path == "/" {
48 ""
49 } else {
50 path.trim_start_matches('/')
51 };
52
53 let route = if base.is_empty() {
54 if sub.is_empty() {
55 "/".to_string()
56 } else {
57 format!("/{}", sub)
58 }
59 } else if sub.is_empty() {
60 base.to_string()
61 } else {
62 format!("{}/{}", base, sub)
63 };
64
65 if let Some(version) = version {
66 let raw = version.trim().trim_matches('/');
67 if raw.is_empty() {
68 return route;
69 }
70 let normalized = if raw.starts_with('v') {
71 raw.to_string()
72 } else {
73 format!("v{}", raw)
74 };
75 if route == "/" {
76 format!("/{}", normalized)
77 } else {
78 format!("/{}{}", normalized, route)
79 }
80 } else {
81 route
82 }
83 }
84
85 pub fn get<H, TState>(self, path: &str, handler: H) -> Self
86 where
87 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
88 TState: 'static,
89 {
90 self.get_with_pipeline(path, handler, Vec::new(), Vec::new(), Vec::new(), None)
91 }
92
93 pub fn post<H, TState>(self, path: &str, handler: H) -> Self
94 where
95 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
96 TState: 'static,
97 {
98 self.post_with_pipeline(path, handler, Vec::new(), Vec::new(), Vec::new(), None)
99 }
100
101 pub fn put<H, TState>(self, path: &str, handler: H) -> Self
102 where
103 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
104 TState: 'static,
105 {
106 self.put_with_pipeline(path, handler, Vec::new(), Vec::new(), Vec::new(), None)
107 }
108
109 pub fn delete<H, TState>(self, path: &str, handler: H) -> Self
110 where
111 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
112 TState: 'static,
113 {
114 self.delete_with_pipeline(path, handler, Vec::new(), Vec::new(), Vec::new(), None)
115 }
116
117 pub fn get_with_pipeline<H, TState>(
118 self,
119 path: &str,
120 handler: H,
121 guards: Vec<Arc<dyn Guard>>,
122 interceptors: Vec<Arc<dyn Interceptor>>,
123 filters: Vec<Arc<dyn ExceptionFilter>>,
124 version: Option<&str>,
125 ) -> Self
126 where
127 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
128 TState: 'static,
129 {
130 self.route_with_pipeline("GET", path, get(handler), guards, interceptors, filters, version)
131 }
132
133 pub fn post_with_pipeline<H, TState>(
134 self,
135 path: &str,
136 handler: H,
137 guards: Vec<Arc<dyn Guard>>,
138 interceptors: Vec<Arc<dyn Interceptor>>,
139 filters: Vec<Arc<dyn ExceptionFilter>>,
140 version: Option<&str>,
141 ) -> Self
142 where
143 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
144 TState: 'static,
145 {
146 self.route_with_pipeline("POST", path, post(handler), guards, interceptors, filters, version)
147 }
148
149 pub fn put_with_pipeline<H, TState>(
150 self,
151 path: &str,
152 handler: H,
153 guards: Vec<Arc<dyn Guard>>,
154 interceptors: Vec<Arc<dyn Interceptor>>,
155 filters: Vec<Arc<dyn ExceptionFilter>>,
156 version: Option<&str>,
157 ) -> Self
158 where
159 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
160 TState: 'static,
161 {
162 self.route_with_pipeline("PUT", path, put(handler), guards, interceptors, filters, version)
163 }
164
165 pub fn delete_with_pipeline<H, TState>(
166 self,
167 path: &str,
168 handler: H,
169 guards: Vec<Arc<dyn Guard>>,
170 interceptors: Vec<Arc<dyn Interceptor>>,
171 filters: Vec<Arc<dyn ExceptionFilter>>,
172 version: Option<&str>,
173 ) -> Self
174 where
175 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
176 TState: 'static,
177 {
178 self.route_with_pipeline("DELETE", path, delete(handler), guards, interceptors, filters, version)
179 }
180
181 fn route_with_pipeline(
182 self,
183 method: &str,
184 path: &str,
185 method_router: axum::routing::MethodRouter<Container>,
186 guards: Vec<Arc<dyn Guard>>,
187 interceptors: Vec<Arc<dyn Interceptor>>,
188 filters: Vec<Arc<dyn ExceptionFilter>>,
189 version: Option<&str>,
190 ) -> Self {
191 let full = Self::full_path(path, version);
192 framework_log_event(
193 "route_register",
194 &[("method", method.to_string()), ("path", full.clone())],
195 );
196 let guards = Arc::new(guards);
197 let interceptors = Arc::new(interceptors);
198 let filters = Arc::new(filters);
199
200 let route = method_router.route_layer(from_fn(move |req, next| {
201 let guards = Arc::clone(&guards);
202 let interceptors = Arc::clone(&interceptors);
203 let filters = Arc::clone(&filters);
204 async move {
205 execute_pipeline(req, next, guards, interceptors, filters).await
206 }
207 }));
208
209 Self {
210 router: self.router.route(&full, route),
211 _marker: std::marker::PhantomData,
212 }
213 }
214
215 pub fn build(self) -> Router<Container> {
216 self.router
217 }
218}