Skip to main content

nestforge_core/
route_builder.rs

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
14/*
15RouteBuilder<T> helps us build routes cleanly in generated code.
16It prefixes method routes using the controller base path from #[controller("...")].
17*/
18pub 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}