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::{execute_pipeline, framework_log, Container, ControllerBasePath, Guard, Interceptor};
10
11/*
12RouteBuilder<T> helps us build routes cleanly in generated code.
13It prefixes method routes using the controller base path from #[controller("...")].
14*/
15pub 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}