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("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}