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