1use std::collections::HashMap;
32use std::future::Future;
33use std::pin::Pin;
34use std::sync::Arc;
35
36use crate::app::{BoxHandler, RouteEntry};
37use crate::context::RequestContext;
38use crate::request::{Method, Request};
39use crate::response::Response;
40
41#[derive(Debug, Clone)]
43pub struct ResponseDef {
44 pub description: String,
46 pub example: Option<serde_json::Value>,
48 pub content_type: Option<String>,
50}
51
52impl ResponseDef {
53 #[must_use]
55 pub fn new(description: impl Into<String>) -> Self {
56 Self {
57 description: description.into(),
58 example: None,
59 content_type: None,
60 }
61 }
62
63 #[must_use]
65 pub fn with_example(mut self, example: serde_json::Value) -> Self {
66 self.example = Some(example);
67 self
68 }
69
70 #[must_use]
72 pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
73 self.content_type = Some(content_type.into());
74 self
75 }
76}
77
78pub type BoxDependency = Arc<
83 dyn Fn(
84 &RequestContext,
85 &mut Request,
86 ) -> Pin<Box<dyn Future<Output = Result<(), Response>> + Send>>
87 + Send
88 + Sync,
89>;
90
91#[derive(Clone)]
96pub struct RouterDependency {
97 pub(crate) handler: BoxDependency,
99 pub(crate) name: String,
101}
102
103impl RouterDependency {
104 pub fn new<F, Fut>(name: impl Into<String>, f: F) -> Self
109 where
110 F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
111 Fut: Future<Output = Result<(), Response>> + Send + 'static,
112 {
113 Self {
114 handler: Arc::new(move |ctx, req| Box::pin(f(ctx, req))),
115 name: name.into(),
116 }
117 }
118
119 pub async fn execute(&self, ctx: &RequestContext, req: &mut Request) -> Result<(), Response> {
121 (self.handler)(ctx, req).await
122 }
123}
124
125impl std::fmt::Debug for RouterDependency {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.debug_struct("RouterDependency")
128 .field("name", &self.name)
129 .finish_non_exhaustive()
130 }
131}
132
133#[derive(Debug, Default, Clone)]
160pub struct IncludeConfig {
161 prefix: Option<String>,
163 tags: Vec<String>,
165 dependencies: Vec<RouterDependency>,
167 responses: HashMap<u16, ResponseDef>,
169 deprecated: Option<bool>,
171 include_in_schema: Option<bool>,
173}
174
175impl IncludeConfig {
176 #[must_use]
178 pub fn new() -> Self {
179 Self::default()
180 }
181
182 #[must_use]
184 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
185 let p = prefix.into();
186 if !p.is_empty() {
187 let normalized = if p.starts_with('/') {
189 p
190 } else {
191 format!("/{}", p)
192 };
193 let normalized = if normalized.ends_with('/') && normalized.len() > 1 {
195 normalized.trim_end_matches('/').to_string()
196 } else {
197 normalized
198 };
199 self.prefix = Some(normalized);
200 }
201 self
202 }
203
204 #[must_use]
206 pub fn tags(mut self, tags: Vec<impl Into<String>>) -> Self {
207 self.tags = tags.into_iter().map(Into::into).collect();
208 self
209 }
210
211 #[must_use]
213 pub fn tag(mut self, tag: impl Into<String>) -> Self {
214 self.tags.push(tag.into());
215 self
216 }
217
218 #[must_use]
220 pub fn dependency(mut self, dep: RouterDependency) -> Self {
221 self.dependencies.push(dep);
222 self
223 }
224
225 #[must_use]
227 pub fn dependencies(mut self, deps: Vec<RouterDependency>) -> Self {
228 self.dependencies = deps;
229 self
230 }
231
232 #[must_use]
234 pub fn response(mut self, status_code: u16, def: ResponseDef) -> Self {
235 self.responses.insert(status_code, def);
236 self
237 }
238
239 #[must_use]
241 pub fn responses(mut self, responses: HashMap<u16, ResponseDef>) -> Self {
242 self.responses = responses;
243 self
244 }
245
246 #[must_use]
248 pub fn deprecated(mut self, deprecated: bool) -> Self {
249 self.deprecated = Some(deprecated);
250 self
251 }
252
253 #[must_use]
255 pub fn include_in_schema(mut self, include: bool) -> Self {
256 self.include_in_schema = Some(include);
257 self
258 }
259
260 #[must_use]
262 pub fn get_prefix(&self) -> Option<&str> {
263 self.prefix.as_deref()
264 }
265
266 #[must_use]
268 pub fn get_tags(&self) -> &[String] {
269 &self.tags
270 }
271
272 #[must_use]
274 pub fn get_dependencies(&self) -> &[RouterDependency] {
275 &self.dependencies
276 }
277
278 #[must_use]
280 pub fn get_responses(&self) -> &HashMap<u16, ResponseDef> {
281 &self.responses
282 }
283
284 #[must_use]
286 pub fn get_deprecated(&self) -> Option<bool> {
287 self.deprecated
288 }
289
290 #[must_use]
292 pub fn get_include_in_schema(&self) -> Option<bool> {
293 self.include_in_schema
294 }
295}
296
297#[derive(Clone)]
299pub struct RouterRoute {
300 pub method: Method,
302 pub path: String,
304 pub(crate) handler: Arc<BoxHandler>,
306 pub tags: Vec<String>,
308 pub dependencies: Vec<RouterDependency>,
310 pub deprecated: Option<bool>,
312 pub include_in_schema: bool,
314}
315
316impl std::fmt::Debug for RouterRoute {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 f.debug_struct("RouterRoute")
319 .field("method", &self.method)
320 .field("path", &self.path)
321 .field("tags", &self.tags)
322 .field("deprecated", &self.deprecated)
323 .field("include_in_schema", &self.include_in_schema)
324 .finish_non_exhaustive()
325 }
326}
327
328#[derive(Debug, Default)]
354pub struct APIRouter {
355 prefix: String,
357 tags: Vec<String>,
359 dependencies: Vec<RouterDependency>,
361 responses: HashMap<u16, ResponseDef>,
363 deprecated: Option<bool>,
365 include_in_schema: bool,
367 routes: Vec<RouterRoute>,
369}
370
371impl APIRouter {
372 #[must_use]
374 pub fn new() -> Self {
375 Self {
376 prefix: String::new(),
377 tags: Vec::new(),
378 dependencies: Vec::new(),
379 responses: HashMap::new(),
380 deprecated: None,
381 include_in_schema: true,
382 routes: Vec::new(),
383 }
384 }
385
386 #[must_use]
388 pub fn with_prefix(prefix: impl Into<String>) -> Self {
389 Self::new().prefix(prefix)
390 }
391
392 #[must_use]
397 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
398 let p = prefix.into();
399 if !p.is_empty() && !p.starts_with('/') {
401 self.prefix = format!("/{}", p);
402 } else {
403 self.prefix = p;
404 }
405 if self.prefix.ends_with('/') && self.prefix.len() > 1 {
407 self.prefix.pop();
408 }
409 self
410 }
411
412 #[must_use]
417 pub fn tags(mut self, tags: Vec<impl Into<String>>) -> Self {
418 self.tags = tags.into_iter().map(Into::into).collect();
419 self
420 }
421
422 #[must_use]
424 pub fn tag(mut self, tag: impl Into<String>) -> Self {
425 self.tags.push(tag.into());
426 self
427 }
428
429 #[must_use]
435 pub fn dependency(mut self, dep: RouterDependency) -> Self {
436 self.dependencies.push(dep);
437 self
438 }
439
440 #[must_use]
442 pub fn dependencies(mut self, deps: Vec<RouterDependency>) -> Self {
443 self.dependencies.extend(deps);
444 self
445 }
446
447 #[must_use]
449 pub fn response(mut self, status_code: u16, def: ResponseDef) -> Self {
450 self.responses.insert(status_code, def);
451 self
452 }
453
454 #[must_use]
456 pub fn responses(mut self, responses: HashMap<u16, ResponseDef>) -> Self {
457 self.responses = responses;
458 self
459 }
460
461 #[must_use]
463 pub fn deprecated(mut self, deprecated: bool) -> Self {
464 self.deprecated = Some(deprecated);
465 self
466 }
467
468 #[must_use]
470 pub fn include_in_schema(mut self, include: bool) -> Self {
471 self.include_in_schema = include;
472 self
473 }
474
475 #[must_use]
477 pub fn route<H, Fut>(mut self, path: impl Into<String>, method: Method, handler: H) -> Self
478 where
479 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
480 Fut: Future<Output = Response> + Send + 'static,
481 {
482 let boxed: BoxHandler = Box::new(move |ctx, req| {
483 let fut = handler(ctx, req);
484 Box::pin(fut)
485 });
486 self.routes.push(RouterRoute {
487 method,
488 path: path.into(),
489 handler: Arc::new(boxed),
490 tags: Vec::new(),
491 dependencies: Vec::new(),
492 deprecated: None,
493 include_in_schema: true,
494 });
495 self
496 }
497
498 #[must_use]
500 pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
501 where
502 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
503 Fut: Future<Output = Response> + Send + 'static,
504 {
505 self.route(path, Method::Get, handler)
506 }
507
508 #[must_use]
510 pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
511 where
512 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
513 Fut: Future<Output = Response> + Send + 'static,
514 {
515 self.route(path, Method::Post, handler)
516 }
517
518 #[must_use]
520 pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
521 where
522 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
523 Fut: Future<Output = Response> + Send + 'static,
524 {
525 self.route(path, Method::Put, handler)
526 }
527
528 #[must_use]
530 pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
531 where
532 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
533 Fut: Future<Output = Response> + Send + 'static,
534 {
535 self.route(path, Method::Delete, handler)
536 }
537
538 #[must_use]
540 pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
541 where
542 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
543 Fut: Future<Output = Response> + Send + 'static,
544 {
545 self.route(path, Method::Patch, handler)
546 }
547
548 #[must_use]
550 pub fn options<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
551 where
552 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
553 Fut: Future<Output = Response> + Send + 'static,
554 {
555 self.route(path, Method::Options, handler)
556 }
557
558 #[must_use]
560 pub fn head<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
561 where
562 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
563 Fut: Future<Output = Response> + Send + 'static,
564 {
565 self.route(path, Method::Head, handler)
566 }
567
568 #[must_use]
586 pub fn include_router(self, other: APIRouter) -> Self {
587 self.include_router_with_config(other, IncludeConfig::default())
588 }
589
590 #[must_use]
623 pub fn include_router_with_config(mut self, other: APIRouter, config: IncludeConfig) -> Self {
624 let effective_deprecated = config.deprecated.or(other.deprecated);
626 let effective_include_in_schema =
627 config.include_in_schema.unwrap_or(other.include_in_schema);
628
629 let full_prefix = match config.prefix.as_deref() {
631 Some(config_prefix) => combine_paths(config_prefix, &other.prefix),
632 None => other.prefix.clone(),
633 };
634
635 for mut route in other.routes {
637 let combined_path = combine_paths(&full_prefix, &route.path);
639 route.path = combined_path;
640
641 let mut merged_tags = config.tags.clone();
643 merged_tags.extend(other.tags.clone());
644 merged_tags.extend(route.tags);
645 route.tags = merged_tags;
646
647 let mut merged_deps = config.dependencies.clone();
649 merged_deps.extend(other.dependencies.clone());
650 merged_deps.extend(route.dependencies);
651 route.dependencies = merged_deps;
652
653 if route.deprecated.is_none() {
655 route.deprecated = effective_deprecated;
656 }
657
658 if !effective_include_in_schema {
660 route.include_in_schema = false;
661 }
662
663 self.routes.push(route);
664 }
665
666 for (code, def) in config.responses {
669 self.responses.entry(code).or_insert(def);
670 }
671 for (code, def) in other.responses {
673 self.responses.insert(code, def);
674 }
675
676 self
677 }
678
679 #[must_use]
681 pub fn get_prefix(&self) -> &str {
682 &self.prefix
683 }
684
685 #[must_use]
687 pub fn get_tags(&self) -> &[String] {
688 &self.tags
689 }
690
691 #[must_use]
693 pub fn get_dependencies(&self) -> &[RouterDependency] {
694 &self.dependencies
695 }
696
697 #[must_use]
699 pub fn get_responses(&self) -> &HashMap<u16, ResponseDef> {
700 &self.responses
701 }
702
703 #[must_use]
705 pub fn is_deprecated(&self) -> Option<bool> {
706 self.deprecated
707 }
708
709 #[must_use]
711 pub fn get_include_in_schema(&self) -> bool {
712 self.include_in_schema
713 }
714
715 #[must_use]
717 pub fn get_routes(&self) -> &[RouterRoute] {
718 &self.routes
719 }
720
721 #[must_use]
726 pub fn into_route_entries(self) -> Vec<RouteEntry> {
727 let prefix = self.prefix;
728 let _router_tags = self.tags;
729 let router_deps = self.dependencies;
730 let _router_deprecated = self.deprecated;
731 let router_include_in_schema = self.include_in_schema;
732
733 self.routes
734 .into_iter()
735 .filter(|route| {
736 router_include_in_schema && route.include_in_schema
738 })
739 .map(move |route| {
740 let full_path = combine_paths(&prefix, &route.path);
742
743 let deps: Vec<RouterDependency> = router_deps
745 .iter()
746 .cloned()
747 .chain(route.dependencies)
748 .collect();
749
750 let handler = route.handler;
751
752 if deps.is_empty() {
754 RouteEntry::new(route.method, full_path, move |ctx, req| {
756 let handler = Arc::clone(&handler);
757 (handler)(ctx, req)
758 })
759 } else {
760 let deps = Arc::new(deps);
762 RouteEntry::new(route.method, full_path, move |ctx, req| {
763 let handler = Arc::clone(&handler);
764 let deps = Arc::clone(&deps);
765 Box::pin(async move {
766 for dep in deps.iter() {
768 if let Err(response) = dep.execute(ctx, req).await {
769 return response;
770 }
771 }
772 (handler)(ctx, req).await
774 })
775 })
776 }
777 })
778 .collect()
779 }
780}
781
782fn combine_paths(prefix: &str, path: &str) -> String {
784 match (prefix.is_empty(), path.is_empty()) {
785 (true, true) => "/".to_string(),
786 (true, false) => {
787 if path.starts_with('/') {
788 path.to_string()
789 } else {
790 format!("/{}", path)
791 }
792 }
793 (false, true) => prefix.to_string(),
794 (false, false) => {
795 let prefix = prefix.trim_end_matches('/');
796 let path = path.trim_start_matches('/');
797 if path.is_empty() {
798 prefix.to_string()
799 } else {
800 format!("{}/{}", prefix, path)
801 }
802 }
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn test_combine_paths() {
812 assert_eq!(combine_paths("", ""), "/");
813 assert_eq!(combine_paths("", "/users"), "/users");
814 assert_eq!(combine_paths("", "users"), "/users");
815 assert_eq!(combine_paths("/api", ""), "/api");
816 assert_eq!(combine_paths("/api", "/users"), "/api/users");
817 assert_eq!(combine_paths("/api", "users"), "/api/users");
818 assert_eq!(combine_paths("/api/", "/users"), "/api/users");
819 assert_eq!(combine_paths("/api/", "users"), "/api/users");
820 }
821
822 #[test]
823 fn test_router_prefix_normalization() {
824 let router = APIRouter::new().prefix("api");
825 assert_eq!(router.get_prefix(), "/api");
826
827 let router = APIRouter::new().prefix("/api/");
828 assert_eq!(router.get_prefix(), "/api");
829
830 let router = APIRouter::new().prefix("/api/v1");
831 assert_eq!(router.get_prefix(), "/api/v1");
832 }
833
834 #[test]
835 fn test_router_tags() {
836 let router = APIRouter::new().tags(vec!["users", "admin"]).tag("api");
837
838 assert_eq!(router.get_tags(), &["users", "admin", "api"]);
839 }
840
841 #[test]
842 fn test_router_deprecated() {
843 let router = APIRouter::new().deprecated(true);
844 assert_eq!(router.is_deprecated(), Some(true));
845
846 let router = APIRouter::new();
847 assert_eq!(router.is_deprecated(), None);
848 }
849
850 #[test]
851 fn test_response_def() {
852 let def = ResponseDef::new("Success")
853 .with_example(serde_json::json!({"id": 1}))
854 .with_content_type("application/json");
855
856 assert_eq!(def.description, "Success");
857 assert_eq!(def.example, Some(serde_json::json!({"id": 1})));
858 assert_eq!(def.content_type, Some("application/json".to_string()));
859 }
860
861 #[test]
862 fn test_include_in_schema() {
863 let router = APIRouter::new().include_in_schema(false);
864 assert!(!router.get_include_in_schema());
865
866 let router = APIRouter::new();
867 assert!(router.get_include_in_schema());
868 }
869
870 #[test]
871 fn test_nested_routers_prefix_combination() {
872 let inner = APIRouter::new().prefix("/items");
874 assert_eq!(inner.get_prefix(), "/items");
875
876 let outer = APIRouter::new().prefix("/api/v1").include_router(inner);
878
879 assert_eq!(outer.get_prefix(), "/api/v1");
882 }
883
884 #[test]
885 fn test_router_with_responses() {
886 let router = APIRouter::new()
887 .response(200, ResponseDef::new("Success"))
888 .response(404, ResponseDef::new("Not Found"));
889
890 let responses = router.get_responses();
891 assert_eq!(responses.len(), 2);
892 assert!(responses.contains_key(&200));
893 assert!(responses.contains_key(&404));
894 }
895
896 #[test]
897 fn test_router_dependency_creation() {
898 let dep = RouterDependency::new("auth", |_ctx, _req| async { Ok(()) });
899 assert_eq!(dep.name, "auth");
900 }
901
902 #[test]
903 fn test_router_with_dependency() {
904 let dep = RouterDependency::new("auth", |_ctx, _req| async { Ok(()) });
905
906 let router = APIRouter::new().dependency(dep);
907 assert_eq!(router.get_dependencies().len(), 1);
908 assert_eq!(router.get_dependencies()[0].name, "auth");
909 }
910
911 #[test]
912 fn test_router_multiple_dependencies() {
913 let dep1 = RouterDependency::new("auth", |_ctx, _req| async { Ok(()) });
914 let dep2 = RouterDependency::new("rate_limit", |_ctx, _req| async { Ok(()) });
915
916 let router = APIRouter::new().dependencies(vec![dep1, dep2]);
917 assert_eq!(router.get_dependencies().len(), 2);
918 }
919
920 #[test]
921 fn test_tag_merging_with_nested_routers() {
922 let inner = APIRouter::new().tags(vec!["items"]);
923 let outer = APIRouter::new().tags(vec!["api"]).include_router(inner);
924
925 assert_eq!(outer.get_tags(), &["api"]);
927 }
929
930 #[test]
931 fn test_with_prefix_constructor() {
932 let router = APIRouter::with_prefix("/api/v1");
933 assert_eq!(router.get_prefix(), "/api/v1");
934 }
935
936 #[test]
937 fn test_empty_router() {
938 let router = APIRouter::new();
939 assert_eq!(router.get_prefix(), "");
940 assert!(router.get_tags().is_empty());
941 assert!(router.get_dependencies().is_empty());
942 assert!(router.get_responses().is_empty());
943 assert_eq!(router.is_deprecated(), None);
944 assert!(router.get_include_in_schema());
945 assert!(router.get_routes().is_empty());
946 }
947
948 #[test]
953 fn test_include_config_default() {
954 let config = IncludeConfig::new();
955 assert!(config.get_prefix().is_none());
956 assert!(config.get_tags().is_empty());
957 assert!(config.get_dependencies().is_empty());
958 assert!(config.get_responses().is_empty());
959 assert!(config.get_deprecated().is_none());
960 assert!(config.get_include_in_schema().is_none());
961 }
962
963 #[test]
964 fn test_include_config_prefix() {
965 let config = IncludeConfig::new().prefix("/api/v1");
966 assert_eq!(config.get_prefix(), Some("/api/v1"));
967
968 let config = IncludeConfig::new().prefix("api/v1");
970 assert_eq!(config.get_prefix(), Some("/api/v1"));
971
972 let config = IncludeConfig::new().prefix("/api/v1/");
974 assert_eq!(config.get_prefix(), Some("/api/v1"));
975 }
976
977 #[test]
978 fn test_include_config_tags() {
979 let config = IncludeConfig::new().tags(vec!["api", "v1"]).tag("extra");
980 assert_eq!(config.get_tags(), &["api", "v1", "extra"]);
981 }
982
983 #[test]
984 fn test_include_config_dependencies() {
985 let dep1 = RouterDependency::new("auth", |_ctx, _req| async { Ok(()) });
986 let dep2 = RouterDependency::new("rate_limit", |_ctx, _req| async { Ok(()) });
987
988 let config = IncludeConfig::new().dependency(dep1).dependency(dep2);
989 assert_eq!(config.get_dependencies().len(), 2);
990 }
991
992 #[test]
993 fn test_include_config_responses() {
994 let config = IncludeConfig::new()
995 .response(401, ResponseDef::new("Unauthorized"))
996 .response(500, ResponseDef::new("Server Error"));
997 assert_eq!(config.get_responses().len(), 2);
998 }
999
1000 #[test]
1001 fn test_include_config_deprecated() {
1002 let config = IncludeConfig::new().deprecated(true);
1003 assert_eq!(config.get_deprecated(), Some(true));
1004
1005 let config = IncludeConfig::new().deprecated(false);
1006 assert_eq!(config.get_deprecated(), Some(false));
1007 }
1008
1009 #[test]
1010 fn test_include_config_include_in_schema() {
1011 let config = IncludeConfig::new().include_in_schema(false);
1012 assert_eq!(config.get_include_in_schema(), Some(false));
1013 }
1014
1015 #[test]
1020 fn test_merge_rule_prefix_prepending() {
1021 let inner_router = APIRouter::new().prefix("/users");
1023 let config = IncludeConfig::new().prefix("/api/v1");
1024
1025 let outer = APIRouter::new().include_router_with_config(inner_router, config);
1026
1027 assert_eq!(outer.get_routes().len(), 0); }
1032
1033 #[test]
1034 fn test_merge_rule_tags_prepending() {
1035 let inner = APIRouter::new().tags(vec!["users"]);
1037 let config = IncludeConfig::new().tags(vec!["api", "v1"]);
1038
1039 let outer = APIRouter::new()
1040 .tags(vec!["outer"])
1041 .include_router_with_config(inner, config);
1042
1043 assert_eq!(outer.get_tags(), &["outer"]);
1045 }
1046
1047 #[test]
1048 fn test_merge_rule_deprecated_override() {
1049 let inner = APIRouter::new().deprecated(false);
1051 let config = IncludeConfig::new().deprecated(true);
1052
1053 let outer = APIRouter::new().include_router_with_config(inner, config);
1054
1055 assert_eq!(outer.is_deprecated(), None);
1057 }
1058
1059 #[test]
1060 fn test_merge_rule_include_in_schema_override() {
1061 let inner = APIRouter::new().include_in_schema(true);
1063 let config = IncludeConfig::new().include_in_schema(false);
1064
1065 let _outer = APIRouter::new().include_router_with_config(inner, config);
1066 }
1068
1069 #[test]
1070 fn test_merge_rule_responses_priority() {
1071 let inner = APIRouter::new()
1073 .response(200, ResponseDef::new("Router Success"))
1074 .response(404, ResponseDef::new("Router Not Found"));
1075
1076 let config = IncludeConfig::new()
1077 .response(200, ResponseDef::new("Config Success"))
1078 .response(500, ResponseDef::new("Config Error"));
1079
1080 let outer = APIRouter::new().include_router_with_config(inner, config);
1081
1082 let responses = outer.get_responses();
1083 assert_eq!(responses.get(&200).unwrap().description, "Router Success");
1085 assert_eq!(responses.get(&500).unwrap().description, "Config Error");
1087 assert_eq!(responses.get(&404).unwrap().description, "Router Not Found");
1089 }
1090
1091 #[test]
1092 fn test_recursive_router_inclusion() {
1093 let level3 = APIRouter::new().prefix("/items");
1095 let level2 = APIRouter::new().prefix("/users").include_router(level3);
1096 let level1 = APIRouter::new().prefix("/api").include_router(level2);
1097
1098 assert_eq!(level1.get_prefix(), "/api");
1101 }
1102
1103 #[test]
1104 fn test_recursive_config_merging() {
1105 let inner = APIRouter::new().tags(vec!["items"]);
1107 let middle_config = IncludeConfig::new().tags(vec!["users"]).prefix("/users");
1108 let outer_config = IncludeConfig::new().tags(vec!["api"]).prefix("/api");
1109
1110 let middle = APIRouter::new().include_router_with_config(inner, middle_config);
1111 let outer = APIRouter::new().include_router_with_config(middle, outer_config);
1112
1113 assert!(outer.get_tags().is_empty());
1115 }
1116
1117 #[test]
1118 fn test_include_config_empty_prefix() {
1119 let inner = APIRouter::new().prefix("/users");
1121 let config = IncludeConfig::new(); let outer = APIRouter::new()
1124 .prefix("/api")
1125 .include_router_with_config(inner, config);
1126
1127 assert_eq!(outer.get_prefix(), "/api");
1129 }
1130
1131 #[test]
1132 fn test_multi_level_path_construction() {
1133 let level1 = "/api";
1139 let level2 = "/v1";
1140 let level3 = "/users";
1141 let level4 = "/{id}";
1142
1143 let combined_12 = combine_paths(level1, level2);
1144 assert_eq!(combined_12, "/api/v1");
1145
1146 let combined_123 = combine_paths(&combined_12, level3);
1147 assert_eq!(combined_123, "/api/v1/users");
1148
1149 let combined_1234 = combine_paths(&combined_123, level4);
1150 assert_eq!(combined_1234, "/api/v1/users/{id}");
1151 }
1152}