mockforge_federation/
router.rs1use crate::federation::Federation;
6use crate::service::ServiceBoundary;
7use chrono::Utc;
8use std::sync::Arc;
9use tracing::debug;
10use uuid::Uuid;
11
12#[derive(Debug, Clone)]
14pub struct RoutingResult {
15 pub workspace_id: Uuid,
17 pub service: Arc<ServiceBoundary>,
19 pub service_path: String,
21}
22
23pub struct FederationRouter {
28 federation: Arc<Federation>,
30}
31
32impl FederationRouter {
33 #[must_use]
35 pub const fn new(federation: Arc<Federation>) -> Self {
36 Self { federation }
37 }
38
39 pub fn route(&self, path: &str) -> Option<RoutingResult> {
43 debug!(path = %path, "Routing request in federation");
44
45 let service = self.federation.find_service_by_path(path)?;
46
47 let service_path = service.extract_service_path(path)?;
48
49 debug!(
50 path = %path,
51 service = %service.name,
52 workspace_id = %service.workspace_id,
53 service_path = %service_path,
54 "Routed request to service"
55 );
56
57 Some(RoutingResult {
58 workspace_id: service.workspace_id,
59 service: Arc::new(service.clone()),
60 service_path,
61 })
62 }
63
64 #[must_use]
66 pub fn services(&self) -> &[ServiceBoundary] {
67 &self.federation.services
68 }
69
70 #[must_use]
72 pub fn federation_id(&self) -> Uuid {
73 self.federation.id
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::service::ServiceRealityLevel;
81
82 fn create_test_federation() -> Arc<Federation> {
83 Arc::new(Federation {
84 id: Uuid::new_v4(),
85 name: "test".to_string(),
86 description: String::new(),
87 org_id: Uuid::new_v4(),
88 services: vec![
89 ServiceBoundary::new(
90 "auth".to_string(),
91 Uuid::new_v4(),
92 "/auth".to_string(),
93 ServiceRealityLevel::Real,
94 ),
95 ServiceBoundary::new(
96 "payments".to_string(),
97 Uuid::new_v4(),
98 "/payments".to_string(),
99 ServiceRealityLevel::MockV3,
100 ),
101 ],
102 created_at: Utc::now(),
103 updated_at: Utc::now(),
104 })
105 }
106
107 #[test]
108 fn test_router() {
109 let federation = create_test_federation();
110 let router = FederationRouter::new(federation);
111 let result = router.route("/auth/login");
112
113 assert!(result.is_some());
114 let routing = result.unwrap();
115 assert_eq!(routing.service_path, "/login");
116 }
117
118 #[test]
119 fn test_router_new() {
120 let federation = create_test_federation();
121 let router = FederationRouter::new(federation.clone());
122 assert_eq!(router.federation_id(), federation.id);
123 }
124
125 #[test]
126 fn test_router_route_exact_path() {
127 let federation = create_test_federation();
128 let router = FederationRouter::new(federation);
129
130 let result = router.route("/auth").unwrap();
131 assert_eq!(result.service_path, "/");
132 assert_eq!(result.service.name, "auth");
133 }
134
135 #[test]
136 fn test_router_route_nested_path() {
137 let federation = create_test_federation();
138 let router = FederationRouter::new(federation);
139
140 let result = router.route("/payments/process/order/123").unwrap();
141 assert_eq!(result.service_path, "/process/order/123");
142 assert_eq!(result.service.name, "payments");
143 }
144
145 #[test]
146 fn test_router_route_no_match() {
147 let federation = create_test_federation();
148 let router = FederationRouter::new(federation);
149
150 assert!(router.route("/unknown").is_none());
151 assert!(router.route("/api/users").is_none());
152 assert!(router.route("").is_none());
153 }
154
155 #[test]
156 fn test_router_services() {
157 let federation = create_test_federation();
158 let router = FederationRouter::new(federation);
159
160 let services = router.services();
161 assert_eq!(services.len(), 2);
162 assert!(services.iter().any(|s| s.name == "auth"));
163 assert!(services.iter().any(|s| s.name == "payments"));
164 }
165
166 #[test]
167 fn test_router_federation_id() {
168 let federation = create_test_federation();
169 let expected_id = federation.id;
170 let router = FederationRouter::new(federation);
171
172 assert_eq!(router.federation_id(), expected_id);
173 }
174
175 #[test]
176 fn test_routing_result_contains_workspace_id() {
177 let federation = create_test_federation();
178 let expected_workspace_id = federation.services[0].workspace_id;
179 let router = FederationRouter::new(federation);
180
181 let result = router.route("/auth/login").unwrap();
182 assert_eq!(result.workspace_id, expected_workspace_id);
183 }
184
185 #[test]
186 fn test_routing_result_debug() {
187 let federation = create_test_federation();
188 let router = FederationRouter::new(federation);
189
190 let result = router.route("/auth/login").unwrap();
191 let debug = format!("{result:?}");
192 assert!(debug.contains("RoutingResult"));
193 assert!(debug.contains("service_path"));
194 }
195
196 #[test]
197 fn test_routing_result_clone() {
198 let federation = create_test_federation();
199 let router = FederationRouter::new(federation);
200
201 let result = router.route("/auth/login").unwrap();
202 let cloned = result.clone();
203
204 assert_eq!(result.workspace_id, cloned.workspace_id);
205 assert_eq!(result.service_path, cloned.service_path);
206 assert_eq!(result.service.name, cloned.service.name);
207 }
208
209 #[test]
210 fn test_router_routes_to_different_services() {
211 let federation = create_test_federation();
212 let router = FederationRouter::new(federation);
213
214 let auth_result = router.route("/auth/login").unwrap();
215 let payment_result = router.route("/payments/process").unwrap();
216
217 assert_eq!(auth_result.service.name, "auth");
218 assert_eq!(payment_result.service.name, "payments");
219 assert_ne!(auth_result.workspace_id, payment_result.workspace_id);
220 }
221
222 #[test]
223 fn test_router_with_empty_services() {
224 let federation = Arc::new(Federation {
225 id: Uuid::new_v4(),
226 name: "empty".to_string(),
227 description: String::new(),
228 org_id: Uuid::new_v4(),
229 services: vec![],
230 created_at: Utc::now(),
231 updated_at: Utc::now(),
232 });
233
234 let router = FederationRouter::new(federation);
235 assert!(router.route("/any/path").is_none());
236 assert!(router.services().is_empty());
237 }
238}