1use super::router::Router;
2use crate::{
3 request::ElifRequest,
4 response::IntoElifResponse,
5 errors::HttpResult,
6 middleware::versioning::{VersioningConfig, ApiVersion},
7};
8use std::collections::HashMap;
9use std::future::Future;
10
11#[derive(Debug)]
13pub struct VersionedRouter<S = ()>
14where
15 S: Clone + Send + Sync + 'static,
16{
17 pub version_routers: HashMap<String, Router<S>>,
19 pub versioning_config: VersioningConfig,
21 pub global_router: Option<Router<S>>,
23 pub base_path: String,
25}
26
27impl<S> VersionedRouter<S>
28where
29 S: Clone + Send + Sync + 'static,
30{
31 pub fn new() -> Self {
33 Self {
34 version_routers: HashMap::new(),
35 versioning_config: VersioningConfig::builder().build().unwrap(),
36 global_router: None,
37 base_path: "/api".to_string(),
38 }
39 }
40
41 pub fn version(mut self, version: &str, router: Router<S>) -> Self {
43 self.version_routers.insert(version.to_string(), router);
44
45 self.versioning_config.add_version(version.to_string(), ApiVersion {
47 version: version.to_string(),
48 deprecated: false,
49 deprecation_message: None,
50 sunset_date: None,
51 is_default: self.version_routers.len() == 1, });
53
54 self
55 }
56
57 pub fn deprecate_version(mut self, version: &str, message: Option<&str>, sunset_date: Option<&str>) -> Self {
59 self.versioning_config.deprecate_version(
60 version,
61 message.map(|s| s.to_string()),
62 sunset_date.map(|s| s.to_string())
63 );
64 self
65 }
66
67 pub fn default_version(mut self, version: &str) -> Self {
69 let (versions, strategy, _, include_deprecation_headers, version_header_name, version_param_name, strict_validation) = self.versioning_config.clone_config();
70
71 let mut new_config = VersioningConfig::builder()
73 .versions(versions)
74 .strategy(strategy)
75 .include_deprecation_headers(include_deprecation_headers)
76 .version_header_name(version_header_name)
77 .version_param_name(version_param_name)
78 .strict_validation(strict_validation)
79 .default_version(Some(version.to_string()))
80 .build().unwrap();
81
82 new_config.add_version(version.to_string(), ApiVersion {
84 version: version.to_string(),
85 deprecated: false,
86 deprecation_message: None,
87 sunset_date: None,
88 is_default: true,
89 });
90
91 self.versioning_config = new_config;
92 self
93 }
94
95 pub fn strategy(mut self, strategy: crate::middleware::versioning::VersionStrategy) -> Self {
97 let (versions, _, default_version, include_deprecation_headers, version_header_name, version_param_name, strict_validation) = self.versioning_config.clone_config();
98
99 self.versioning_config = VersioningConfig::builder()
101 .versions(versions)
102 .strategy(strategy)
103 .include_deprecation_headers(include_deprecation_headers)
104 .version_header_name(version_header_name)
105 .version_param_name(version_param_name)
106 .strict_validation(strict_validation)
107 .default_version(default_version)
108 .build().unwrap();
109 self
110 }
111
112 pub fn global(mut self, router: Router<S>) -> Self {
114 self.global_router = Some(router);
115 self
116 }
117
118 pub fn build(self) -> Router<S> {
120 let mut final_router = Router::new();
121
122 if let Some(global_router) = self.global_router {
124 final_router = final_router.merge(global_router);
125 }
126
127 for (version, version_router) in self.version_routers {
129 let version_path = match self.versioning_config.get_strategy() {
130 crate::middleware::versioning::VersionStrategy::UrlPath => {
131 format!("{}/{}", self.base_path, version)
132 },
133 _ => {
134 self.base_path.clone()
136 }
137 };
138
139 final_router = final_router.nest(&version_path, version_router);
141 }
142
143 let versioning_layer = crate::middleware::versioning::versioning_layer(self.versioning_config);
146
147 let axum_router = final_router.into_axum_router();
149 let layered_router = axum_router.layer(versioning_layer);
150
151 Router::new().merge_axum(layered_router)
154 }
155
156 pub fn version_builder<'a>(&'a mut self, version: &str) -> VersionedRouteBuilder<'a, S> {
158 VersionedRouteBuilder::new(version, self)
159 }
160}
161
162pub struct VersionedRouteBuilder<'a, S>
164where
165 S: Clone + Send + Sync + 'static,
166{
167 version: String,
168 router: &'a mut VersionedRouter<S>,
169 current_router: Router<S>,
170}
171
172impl<'a, S> VersionedRouteBuilder<'a, S>
173where
174 S: Clone + Send + Sync + 'static,
175{
176 fn new(version: &str, router: &'a mut VersionedRouter<S>) -> Self {
177 Self {
178 version: version.to_string(),
179 router,
180 current_router: Router::new(),
181 }
182 }
183
184 pub fn get<F, Fut, R>(mut self, path: &str, handler: F) -> Self
186 where
187 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
188 Fut: Future<Output = HttpResult<R>> + Send + 'static,
189 R: IntoElifResponse + Send + 'static,
190 {
191 self.current_router = self.current_router.get(path, handler);
192 self
193 }
194
195 pub fn post<F, Fut, R>(mut self, path: &str, handler: F) -> Self
197 where
198 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
199 Fut: Future<Output = HttpResult<R>> + Send + 'static,
200 R: IntoElifResponse + Send + 'static,
201 {
202 self.current_router = self.current_router.post(path, handler);
203 self
204 }
205
206 pub fn put<F, Fut, R>(mut self, path: &str, handler: F) -> Self
208 where
209 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
210 Fut: Future<Output = HttpResult<R>> + Send + 'static,
211 R: IntoElifResponse + Send + 'static,
212 {
213 self.current_router = self.current_router.put(path, handler);
214 self
215 }
216
217 pub fn delete<F, Fut, R>(mut self, path: &str, handler: F) -> Self
219 where
220 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
221 Fut: Future<Output = HttpResult<R>> + Send + 'static,
222 R: IntoElifResponse + Send + 'static,
223 {
224 self.current_router = self.current_router.delete(path, handler);
225 self
226 }
227
228 pub fn patch<F, Fut, R>(mut self, path: &str, handler: F) -> Self
230 where
231 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
232 Fut: Future<Output = HttpResult<R>> + Send + 'static,
233 R: IntoElifResponse + Send + 'static,
234 {
235 self.current_router = self.current_router.patch(path, handler);
236 self
237 }
238
239 pub fn finish(self) {
241 self.router.version_routers.insert(self.version.clone(), self.current_router);
242 }
243}
244
245pub fn versioned_router<S>() -> VersionedRouter<S>
247where
248 S: Clone + Send + Sync + 'static,
249{
250 VersionedRouter::<S>::new()
251}
252
253pub fn path_versioned_router<S>() -> VersionedRouter<S>
255where
256 S: Clone + Send + Sync + 'static,
257{
258 VersionedRouter::<S> {
259 version_routers: HashMap::new(),
260 versioning_config: VersioningConfig::builder()
261 .strategy(crate::middleware::versioning::VersionStrategy::UrlPath)
262 .build().unwrap(),
263 global_router: None,
264 base_path: "/api".to_string(),
265 }
266}
267
268pub fn header_versioned_router<S>(header_name: &str) -> VersionedRouter<S>
270where
271 S: Clone + Send + Sync + 'static,
272{
273 VersionedRouter::<S> {
274 version_routers: HashMap::new(),
275 versioning_config: VersioningConfig::builder()
276 .strategy(crate::middleware::versioning::VersionStrategy::Header(header_name.to_string()))
277 .build().unwrap(),
278 global_router: None,
279 base_path: "/api".to_string(),
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::response::ElifJson;
287
288 #[tokio::test]
289 async fn test_versioned_router_creation() {
290 let router = VersionedRouter::<()>::new()
291 .version("v1", Router::new())
292 .version("v2", Router::new())
293 .default_version("v1")
294 .deprecate_version("v1", Some("Please use v2"), Some("2024-12-31"));
295
296 assert_eq!(router.version_routers.len(), 2);
297 assert!(router.version_routers.contains_key("v1"));
298 assert!(router.version_routers.contains_key("v2"));
299
300 let v1_version = router.versioning_config.get_version("v1").unwrap();
301 assert!(v1_version.deprecated);
302 assert_eq!(v1_version.deprecation_message, Some("Please use v2".to_string()));
303 }
304
305 #[tokio::test]
306 async fn test_version_builder() {
307 let mut router = VersionedRouter::<()>::new();
308
309 router.version_builder("v1")
310 .get("/users", |_req| async { Ok(ElifJson("users v1")) })
311 .post("/users", |_req| async { Ok(ElifJson("create user v1")) })
312 .finish();
313
314 assert!(router.version_routers.contains_key("v1"));
315 }
316
317 #[test]
318 fn test_convenience_functions() {
319 let _path_router = path_versioned_router::<()>();
320 let _header_router = header_versioned_router::<()>("Api-Version");
321 let _versioned_router = versioned_router::<()>();
322 }
323}