1use super::router::Router;
2use crate::{
3 errors::HttpResult,
4 middleware::versioning::{ApiVersion, VersioningConfig},
5 request::ElifRequest,
6 response::IntoElifResponse,
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> Default for VersionedRouter<S>
28where
29 S: Clone + Send + Sync + 'static,
30{
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl<S> VersionedRouter<S>
37where
38 S: Clone + Send + Sync + 'static,
39{
40 pub fn new() -> Self {
42 Self {
43 version_routers: HashMap::new(),
44 versioning_config: VersioningConfig::builder().build().unwrap(),
45 global_router: None,
46 base_path: "/api".to_string(),
47 }
48 }
49
50 pub fn version(mut self, version: &str, router: Router<S>) -> Self {
52 self.version_routers.insert(version.to_string(), router);
53
54 self.versioning_config.add_version(
56 version.to_string(),
57 ApiVersion {
58 version: version.to_string(),
59 deprecated: false,
60 deprecation_message: None,
61 sunset_date: None,
62 is_default: self.version_routers.len() == 1, },
64 );
65
66 self
67 }
68
69 pub fn deprecate_version(
71 mut self,
72 version: &str,
73 message: Option<&str>,
74 sunset_date: Option<&str>,
75 ) -> Self {
76 self.versioning_config.deprecate_version(
77 version,
78 message.map(|s| s.to_string()),
79 sunset_date.map(|s| s.to_string()),
80 );
81 self
82 }
83
84 pub fn default_version(mut self, version: &str) -> Self {
86 let (
87 versions,
88 strategy,
89 _,
90 include_deprecation_headers,
91 version_header_name,
92 version_param_name,
93 strict_validation,
94 ) = self.versioning_config.clone_config();
95
96 let mut new_config = VersioningConfig::builder()
98 .versions(versions)
99 .strategy(strategy)
100 .include_deprecation_headers(include_deprecation_headers)
101 .version_header_name(version_header_name)
102 .version_param_name(version_param_name)
103 .strict_validation(strict_validation)
104 .default_version(Some(version.to_string()))
105 .build()
106 .unwrap();
107
108 new_config.add_version(
110 version.to_string(),
111 ApiVersion {
112 version: version.to_string(),
113 deprecated: false,
114 deprecation_message: None,
115 sunset_date: None,
116 is_default: true,
117 },
118 );
119
120 self.versioning_config = new_config;
121 self
122 }
123
124 pub fn strategy(mut self, strategy: crate::middleware::versioning::VersionStrategy) -> Self {
126 let (
127 versions,
128 _,
129 default_version,
130 include_deprecation_headers,
131 version_header_name,
132 version_param_name,
133 strict_validation,
134 ) = self.versioning_config.clone_config();
135
136 self.versioning_config = VersioningConfig::builder()
138 .versions(versions)
139 .strategy(strategy)
140 .include_deprecation_headers(include_deprecation_headers)
141 .version_header_name(version_header_name)
142 .version_param_name(version_param_name)
143 .strict_validation(strict_validation)
144 .default_version(default_version)
145 .build()
146 .unwrap();
147 self
148 }
149
150 pub fn global(mut self, router: Router<S>) -> Self {
152 self.global_router = Some(router);
153 self
154 }
155
156 pub fn build(self) -> Router<S> {
158 let mut final_router = Router::new();
159
160 if let Some(global_router) = self.global_router {
162 final_router = final_router.merge(global_router);
163 }
164
165 for (version, version_router) in self.version_routers {
167 let version_path = match self.versioning_config.get_strategy() {
168 crate::middleware::versioning::VersionStrategy::UrlPath => {
169 format!("{}/{}", self.base_path, version)
170 }
171 _ => {
172 self.base_path.clone()
174 }
175 };
176
177 final_router = final_router.nest(&version_path, version_router);
179 }
180
181 let versioning_layer =
184 crate::middleware::versioning::versioning_layer(self.versioning_config);
185
186 let axum_router = final_router.into_axum_router();
188 let layered_router = axum_router.layer(versioning_layer);
189
190 Router::new().merge_axum(layered_router)
193 }
194
195 pub fn version_builder<'a>(&'a mut self, version: &str) -> VersionedRouteBuilder<'a, S> {
197 VersionedRouteBuilder::new(version, self)
198 }
199}
200
201pub struct VersionedRouteBuilder<'a, S>
203where
204 S: Clone + Send + Sync + 'static,
205{
206 version: String,
207 router: &'a mut VersionedRouter<S>,
208 current_router: Router<S>,
209}
210
211impl<'a, S> VersionedRouteBuilder<'a, S>
212where
213 S: Clone + Send + Sync + 'static,
214{
215 fn new(version: &str, router: &'a mut VersionedRouter<S>) -> Self {
216 Self {
217 version: version.to_string(),
218 router,
219 current_router: Router::new(),
220 }
221 }
222
223 pub fn get<F, Fut, R>(mut self, path: &str, handler: F) -> Self
225 where
226 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
227 Fut: Future<Output = HttpResult<R>> + Send + 'static,
228 R: IntoElifResponse + Send + 'static,
229 {
230 self.current_router = self.current_router.get(path, handler);
231 self
232 }
233
234 pub fn post<F, Fut, R>(mut self, path: &str, handler: F) -> Self
236 where
237 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
238 Fut: Future<Output = HttpResult<R>> + Send + 'static,
239 R: IntoElifResponse + Send + 'static,
240 {
241 self.current_router = self.current_router.post(path, handler);
242 self
243 }
244
245 pub fn put<F, Fut, R>(mut self, path: &str, handler: F) -> Self
247 where
248 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
249 Fut: Future<Output = HttpResult<R>> + Send + 'static,
250 R: IntoElifResponse + Send + 'static,
251 {
252 self.current_router = self.current_router.put(path, handler);
253 self
254 }
255
256 pub fn delete<F, Fut, R>(mut self, path: &str, handler: F) -> Self
258 where
259 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
260 Fut: Future<Output = HttpResult<R>> + Send + 'static,
261 R: IntoElifResponse + Send + 'static,
262 {
263 self.current_router = self.current_router.delete(path, handler);
264 self
265 }
266
267 pub fn patch<F, Fut, R>(mut self, path: &str, handler: F) -> Self
269 where
270 F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
271 Fut: Future<Output = HttpResult<R>> + Send + 'static,
272 R: IntoElifResponse + Send + 'static,
273 {
274 self.current_router = self.current_router.patch(path, handler);
275 self
276 }
277
278 pub fn finish(self) {
280 self.router
281 .version_routers
282 .insert(self.version.clone(), self.current_router);
283 }
284}
285
286pub fn versioned_router<S>() -> VersionedRouter<S>
288where
289 S: Clone + Send + Sync + 'static,
290{
291 VersionedRouter::<S>::new()
292}
293
294pub fn path_versioned_router<S>() -> VersionedRouter<S>
296where
297 S: Clone + Send + Sync + 'static,
298{
299 VersionedRouter::<S> {
300 version_routers: HashMap::new(),
301 versioning_config: VersioningConfig::builder()
302 .strategy(crate::middleware::versioning::VersionStrategy::UrlPath)
303 .build()
304 .unwrap(),
305 global_router: None,
306 base_path: "/api".to_string(),
307 }
308}
309
310pub fn header_versioned_router<S>(header_name: &str) -> VersionedRouter<S>
312where
313 S: Clone + Send + Sync + 'static,
314{
315 VersionedRouter::<S> {
316 version_routers: HashMap::new(),
317 versioning_config: VersioningConfig::builder()
318 .strategy(crate::middleware::versioning::VersionStrategy::Header(
319 header_name.to_string(),
320 ))
321 .build()
322 .unwrap(),
323 global_router: None,
324 base_path: "/api".to_string(),
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::response::ElifJson;
332
333 #[tokio::test]
334 async fn test_versioned_router_creation() {
335 let router = VersionedRouter::<()>::new()
336 .version("v1", Router::new())
337 .version("v2", Router::new())
338 .default_version("v1")
339 .deprecate_version("v1", Some("Please use v2"), Some("2024-12-31"));
340
341 assert_eq!(router.version_routers.len(), 2);
342 assert!(router.version_routers.contains_key("v1"));
343 assert!(router.version_routers.contains_key("v2"));
344
345 let v1_version = router.versioning_config.get_version("v1").unwrap();
346 assert!(v1_version.deprecated);
347 assert_eq!(
348 v1_version.deprecation_message,
349 Some("Please use v2".to_string())
350 );
351 }
352
353 #[tokio::test]
354 async fn test_version_builder() {
355 let mut router = VersionedRouter::<()>::new();
356
357 router
358 .version_builder("v1")
359 .get("/users", |_req| async { Ok(ElifJson("users v1")) })
360 .post("/users", |_req| async { Ok(ElifJson("create user v1")) })
361 .finish();
362
363 assert!(router.version_routers.contains_key("v1"));
364 }
365
366 #[test]
367 fn test_convenience_functions() {
368 let _path_router = path_versioned_router::<()>();
369 let _header_router = header_versioned_router::<()>("Api-Version");
370 let _versioned_router = versioned_router::<()>();
371 }
372}