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(
131 "GET",
132 path,
133 get(handler),
134 guards,
135 interceptors,
136 filters,
137 version,
138 )
139 }
140
141 pub fn post_with_pipeline<H, TState>(
142 self,
143 path: &str,
144 handler: H,
145 guards: Vec<Arc<dyn Guard>>,
146 interceptors: Vec<Arc<dyn Interceptor>>,
147 filters: Vec<Arc<dyn ExceptionFilter>>,
148 version: Option<&str>,
149 ) -> Self
150 where
151 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
152 TState: 'static,
153 {
154 self.route_with_pipeline(
155 "POST",
156 path,
157 post(handler),
158 guards,
159 interceptors,
160 filters,
161 version,
162 )
163 }
164
165 pub fn put_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(
179 "PUT",
180 path,
181 put(handler),
182 guards,
183 interceptors,
184 filters,
185 version,
186 )
187 }
188
189 pub fn delete_with_pipeline<H, TState>(
190 self,
191 path: &str,
192 handler: H,
193 guards: Vec<Arc<dyn Guard>>,
194 interceptors: Vec<Arc<dyn Interceptor>>,
195 filters: Vec<Arc<dyn ExceptionFilter>>,
196 version: Option<&str>,
197 ) -> Self
198 where
199 H: axum::handler::Handler<TState, Container> + Clone + Send + Sync + 'static,
200 TState: 'static,
201 {
202 self.route_with_pipeline(
203 "DELETE",
204 path,
205 delete(handler),
206 guards,
207 interceptors,
208 filters,
209 version,
210 )
211 }
212
213 fn route_with_pipeline(
214 self,
215 method: &str,
216 path: &str,
217 method_router: axum::routing::MethodRouter<Container>,
218 guards: Vec<Arc<dyn Guard>>,
219 interceptors: Vec<Arc<dyn Interceptor>>,
220 filters: Vec<Arc<dyn ExceptionFilter>>,
221 version: Option<&str>,
222 ) -> Self {
223 let full = Self::full_path(path, version);
224 framework_log_event(
225 "route_register",
226 &[("method", method.to_string()), ("path", full.clone())],
227 );
228 let guards = Arc::new(guards);
229 let interceptors = Arc::new(interceptors);
230 let filters = Arc::new(filters);
231
232 let route = method_router.route_layer(from_fn(move |req, next| {
233 let guards = Arc::clone(&guards);
234 let interceptors = Arc::clone(&interceptors);
235 let filters = Arc::clone(&filters);
236 async move { execute_pipeline(req, next, guards, interceptors, filters).await }
237 }));
238
239 Self {
240 router: self.router.route(&full, route),
241 _marker: std::marker::PhantomData,
242 }
243 }
244
245 pub fn build(self) -> Router<Container> {
246 self.router
247 }
248}