1use crate::Result;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
10#[serde(rename_all = "lowercase")]
11pub enum HttpMethod {
12 GET,
14 POST,
16 PUT,
18 DELETE,
20 PATCH,
22 HEAD,
24 OPTIONS,
26}
27
28#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
30pub struct Route {
31 pub method: HttpMethod,
33 pub path: String,
35 pub priority: i32,
37 pub metadata: HashMap<String, serde_json::Value>,
39}
40
41impl Route {
42 pub fn new(method: HttpMethod, path: String) -> Self {
44 Self {
45 method,
46 path,
47 priority: 0,
48 metadata: HashMap::new(),
49 }
50 }
51
52 pub fn with_priority(mut self, priority: i32) -> Self {
54 self.priority = priority;
55 self
56 }
57
58 pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
60 self.metadata.insert(key, value);
61 self
62 }
63}
64
65fn to_matchit_pattern(pattern: &str) -> String {
70 if !pattern.contains('*') {
71 return pattern.to_string();
72 }
73
74 pattern
75 .split('/')
76 .enumerate()
77 .map(|(i, seg)| {
78 if seg == "*" {
79 format!("{{w{i}}}")
80 } else {
81 seg.to_string()
82 }
83 })
84 .collect::<Vec<_>>()
85 .join("/")
86}
87
88#[derive(Clone)]
93struct MethodIndex {
94 router: matchit::Router<Vec<usize>>,
96 routes: Vec<Route>,
98}
99
100impl std::fmt::Debug for MethodIndex {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.debug_struct("MethodIndex").field("routes", &self.routes).finish()
103 }
104}
105
106impl MethodIndex {
107 fn new() -> Self {
108 Self {
109 router: matchit::Router::new(),
110 routes: Vec::new(),
111 }
112 }
113
114 fn insert(&mut self, route: Route) {
115 let idx = self.routes.len();
116 let matchit_path = to_matchit_pattern(&route.path);
117 self.routes.push(route);
118
119 match self.router.insert(matchit_path.clone(), vec![idx]) {
122 Ok(()) => {}
123 Err(_) => {
124 if let Ok(matched) = self.router.at_mut(&matchit_path) {
126 matched.value.push(idx);
127 }
128 }
129 }
130 }
131
132 fn find(&self, path: &str) -> Vec<&Route> {
133 match self.router.at(path) {
134 Ok(matched) => matched.value.iter().map(|&i| &self.routes[i]).collect(),
135 Err(_) => Vec::new(),
136 }
137 }
138
139 fn all(&self) -> Vec<&Route> {
140 self.routes.iter().collect()
141 }
142}
143
144#[derive(Debug, Clone)]
149pub struct RouteRegistry {
150 http_routes: HashMap<HttpMethod, MethodIndex>,
152 ws_routes: Vec<Route>,
154 grpc_routes: HashMap<String, Vec<Route>>,
156}
157
158impl RouteRegistry {
159 pub fn new() -> Self {
161 Self {
162 http_routes: HashMap::new(),
163 ws_routes: Vec::new(),
164 grpc_routes: HashMap::new(),
165 }
166 }
167
168 pub fn add_http_route(&mut self, route: Route) -> Result<()> {
170 self.http_routes
171 .entry(route.method.clone())
172 .or_insert_with(MethodIndex::new)
173 .insert(route);
174 Ok(())
175 }
176
177 pub fn add_ws_route(&mut self, route: Route) -> Result<()> {
179 self.ws_routes.push(route);
180 Ok(())
181 }
182
183 pub fn clear(&mut self) {
185 self.http_routes.clear();
186 self.ws_routes.clear();
187 self.grpc_routes.clear();
188 }
189
190 pub fn add_route(&mut self, route: Route) -> Result<()> {
192 self.add_http_route(route)
193 }
194
195 pub fn add_grpc_route(&mut self, service: String, route: Route) -> Result<()> {
197 self.grpc_routes.entry(service).or_default().push(route);
198 Ok(())
199 }
200
201 pub fn find_http_routes(&self, method: &HttpMethod, path: &str) -> Vec<&Route> {
203 self.http_routes.get(method).map(|index| index.find(path)).unwrap_or_default()
204 }
205
206 pub fn find_ws_routes(&self, path: &str) -> Vec<&Route> {
208 self.ws_routes
209 .iter()
210 .filter(|route| self.matches_path(&route.path, path))
211 .collect()
212 }
213
214 pub fn find_grpc_routes(&self, service: &str, method: &str) -> Vec<&Route> {
216 self.grpc_routes
217 .get(service)
218 .map(|routes| {
219 routes.iter().filter(|route| self.matches_path(&route.path, method)).collect()
220 })
221 .unwrap_or_default()
222 }
223
224 fn matches_path(&self, pattern: &str, path: &str) -> bool {
226 if pattern == path {
227 return true;
228 }
229
230 if pattern.contains('*') {
232 let pattern_parts: Vec<&str> = pattern.split('/').collect();
233 let path_parts: Vec<&str> = path.split('/').collect();
234
235 if pattern_parts.len() != path_parts.len() {
236 return false;
237 }
238
239 for (pattern_part, path_part) in pattern_parts.iter().zip(path_parts.iter()) {
240 if *pattern_part != "*" && *pattern_part != *path_part {
241 return false;
242 }
243 }
244 return true;
245 }
246
247 false
248 }
249
250 pub fn get_http_routes(&self, method: &HttpMethod) -> Vec<&Route> {
252 self.http_routes.get(method).map(|index| index.all()).unwrap_or_default()
253 }
254
255 pub fn get_ws_routes(&self) -> Vec<&Route> {
257 self.ws_routes.iter().collect()
258 }
259
260 pub fn get_grpc_routes(&self, service: &str) -> Vec<&Route> {
262 self.grpc_routes
263 .get(service)
264 .map(|routes| routes.iter().collect())
265 .unwrap_or_default()
266 }
267}
268
269impl Default for RouteRegistry {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_route_new() {
281 let route = Route::new(HttpMethod::GET, "/api/users".to_string());
282 assert_eq!(route.method, HttpMethod::GET);
283 assert_eq!(route.path, "/api/users");
284 assert_eq!(route.priority, 0);
285 assert!(route.metadata.is_empty());
286 }
287
288 #[test]
289 fn test_route_with_priority() {
290 let route = Route::new(HttpMethod::POST, "/api/users".to_string()).with_priority(10);
291 assert_eq!(route.priority, 10);
292 }
293
294 #[test]
295 fn test_route_with_metadata() {
296 let route = Route::new(HttpMethod::GET, "/api/users".to_string())
297 .with_metadata("version".to_string(), serde_json::json!("v1"))
298 .with_metadata("auth".to_string(), serde_json::json!(true));
299
300 assert_eq!(route.metadata.get("version"), Some(&serde_json::json!("v1")));
301 assert_eq!(route.metadata.get("auth"), Some(&serde_json::json!(true)));
302 }
303
304 #[test]
305 fn test_route_registry_new() {
306 let registry = RouteRegistry::new();
307 assert!(registry.http_routes.is_empty());
308 assert!(registry.ws_routes.is_empty());
309 assert!(registry.grpc_routes.is_empty());
310 }
311
312 #[test]
313 fn test_route_registry_default() {
314 let registry = RouteRegistry::default();
315 assert!(registry.http_routes.is_empty());
316 }
317
318 #[test]
319 fn test_add_http_route() {
320 let mut registry = RouteRegistry::new();
321 let route = Route::new(HttpMethod::GET, "/api/users".to_string());
322
323 assert!(registry.add_http_route(route).is_ok());
324 assert_eq!(registry.get_http_routes(&HttpMethod::GET).len(), 1);
325 }
326
327 #[test]
328 fn test_add_multiple_http_routes() {
329 let mut registry = RouteRegistry::new();
330
331 registry
332 .add_http_route(Route::new(HttpMethod::GET, "/api/users".to_string()))
333 .unwrap();
334 registry
335 .add_http_route(Route::new(HttpMethod::GET, "/api/posts".to_string()))
336 .unwrap();
337 registry
338 .add_http_route(Route::new(HttpMethod::POST, "/api/users".to_string()))
339 .unwrap();
340
341 assert_eq!(registry.get_http_routes(&HttpMethod::GET).len(), 2);
342 assert_eq!(registry.get_http_routes(&HttpMethod::POST).len(), 1);
343 }
344
345 #[test]
346 fn test_add_ws_route() {
347 let mut registry = RouteRegistry::new();
348 let route = Route::new(HttpMethod::GET, "/ws/chat".to_string());
349
350 assert!(registry.add_ws_route(route).is_ok());
351 assert_eq!(registry.get_ws_routes().len(), 1);
352 }
353
354 #[test]
355 fn test_add_grpc_route() {
356 let mut registry = RouteRegistry::new();
357 let route = Route::new(HttpMethod::POST, "GetUser".to_string());
358
359 assert!(registry.add_grpc_route("UserService".to_string(), route).is_ok());
360 assert_eq!(registry.get_grpc_routes("UserService").len(), 1);
361 }
362
363 #[test]
364 fn test_add_route_alias() {
365 let mut registry = RouteRegistry::new();
366 let route = Route::new(HttpMethod::GET, "/api/test".to_string());
367
368 assert!(registry.add_route(route).is_ok());
369 assert_eq!(registry.get_http_routes(&HttpMethod::GET).len(), 1);
370 }
371
372 #[test]
373 fn test_clear() {
374 let mut registry = RouteRegistry::new();
375
376 registry
377 .add_http_route(Route::new(HttpMethod::GET, "/api/users".to_string()))
378 .unwrap();
379 registry
380 .add_ws_route(Route::new(HttpMethod::GET, "/ws/chat".to_string()))
381 .unwrap();
382 registry
383 .add_grpc_route(
384 "Service".to_string(),
385 Route::new(HttpMethod::POST, "Method".to_string()),
386 )
387 .unwrap();
388
389 assert!(!registry.get_http_routes(&HttpMethod::GET).is_empty());
390 assert!(!registry.get_ws_routes().is_empty());
391
392 registry.clear();
393
394 assert!(registry.get_http_routes(&HttpMethod::GET).is_empty());
395 assert!(registry.get_ws_routes().is_empty());
396 assert!(registry.get_grpc_routes("Service").is_empty());
397 }
398
399 #[test]
400 fn test_find_http_routes_exact_match() {
401 let mut registry = RouteRegistry::new();
402 registry
403 .add_http_route(Route::new(HttpMethod::GET, "/api/users".to_string()))
404 .unwrap();
405
406 let found = registry.find_http_routes(&HttpMethod::GET, "/api/users");
407 assert_eq!(found.len(), 1);
408 assert_eq!(found[0].path, "/api/users");
409 }
410
411 #[test]
412 fn test_find_http_routes_no_match() {
413 let mut registry = RouteRegistry::new();
414 registry
415 .add_http_route(Route::new(HttpMethod::GET, "/api/users".to_string()))
416 .unwrap();
417
418 let found = registry.find_http_routes(&HttpMethod::GET, "/api/posts");
419 assert_eq!(found.len(), 0);
420 }
421
422 #[test]
423 fn test_find_http_routes_wildcard_match() {
424 let mut registry = RouteRegistry::new();
425 registry
426 .add_http_route(Route::new(HttpMethod::GET, "/api/*/details".to_string()))
427 .unwrap();
428
429 let found = registry.find_http_routes(&HttpMethod::GET, "/api/users/details");
430 assert_eq!(found.len(), 1);
431
432 let found = registry.find_http_routes(&HttpMethod::GET, "/api/posts/details");
433 assert_eq!(found.len(), 1);
434 }
435
436 #[test]
437 fn test_find_http_routes_wildcard_no_match_different_length() {
438 let mut registry = RouteRegistry::new();
439 registry
440 .add_http_route(Route::new(HttpMethod::GET, "/api/*/details".to_string()))
441 .unwrap();
442
443 let found = registry.find_http_routes(&HttpMethod::GET, "/api/users");
444 assert_eq!(found.len(), 0);
445 }
446
447 #[test]
448 fn test_find_ws_routes() {
449 let mut registry = RouteRegistry::new();
450 registry
451 .add_ws_route(Route::new(HttpMethod::GET, "/ws/chat".to_string()))
452 .unwrap();
453
454 let found = registry.find_ws_routes("/ws/chat");
455 assert_eq!(found.len(), 1);
456 }
457
458 #[test]
459 fn test_find_ws_routes_wildcard() {
460 let mut registry = RouteRegistry::new();
461 registry.add_ws_route(Route::new(HttpMethod::GET, "/ws/*".to_string())).unwrap();
462
463 let found = registry.find_ws_routes("/ws/chat");
464 assert_eq!(found.len(), 1);
465
466 let found = registry.find_ws_routes("/ws/notifications");
467 assert_eq!(found.len(), 1);
468 }
469
470 #[test]
471 fn test_find_grpc_routes() {
472 let mut registry = RouteRegistry::new();
473 registry
474 .add_grpc_route(
475 "UserService".to_string(),
476 Route::new(HttpMethod::POST, "GetUser".to_string()),
477 )
478 .unwrap();
479
480 let found = registry.find_grpc_routes("UserService", "GetUser");
481 assert_eq!(found.len(), 1);
482 }
483
484 #[test]
485 fn test_find_grpc_routes_wildcard() {
486 let mut registry = RouteRegistry::new();
487 registry
488 .add_grpc_route(
489 "UserService".to_string(),
490 Route::new(HttpMethod::POST, "GetUser".to_string()),
491 )
492 .unwrap();
493
494 let found = registry.find_grpc_routes("UserService", "GetUser");
495 assert_eq!(found.len(), 1);
496 }
497
498 #[test]
499 fn test_matches_path_exact() {
500 let registry = RouteRegistry::new();
501 assert!(registry.matches_path("/api/users", "/api/users"));
502 assert!(!registry.matches_path("/api/users", "/api/posts"));
503 }
504
505 #[test]
506 fn test_matches_path_wildcard_single_segment() {
507 let registry = RouteRegistry::new();
508 assert!(registry.matches_path("/api/*", "/api/users"));
509 assert!(registry.matches_path("/api/*", "/api/posts"));
510 assert!(!registry.matches_path("/api/*", "/api"));
511 assert!(!registry.matches_path("/api/*", "/api/users/123"));
512 }
513
514 #[test]
515 fn test_matches_path_wildcard_multiple_segments() {
516 let registry = RouteRegistry::new();
517 assert!(registry.matches_path("/api/*/details", "/api/users/details"));
518 assert!(registry.matches_path("/api/*/*", "/api/users/123"));
519 assert!(!registry.matches_path("/api/*/*", "/api/users"));
520 }
521
522 #[test]
523 fn test_get_http_routes_empty() {
524 let registry = RouteRegistry::new();
525 assert!(registry.get_http_routes(&HttpMethod::GET).is_empty());
526 }
527
528 #[test]
529 fn test_get_ws_routes_empty() {
530 let registry = RouteRegistry::new();
531 assert!(registry.get_ws_routes().is_empty());
532 }
533
534 #[test]
535 fn test_get_grpc_routes_empty() {
536 let registry = RouteRegistry::new();
537 assert!(registry.get_grpc_routes("Service").is_empty());
538 }
539
540 #[test]
541 fn test_http_method_serialization() {
542 let method = HttpMethod::GET;
543 let json = serde_json::to_string(&method).unwrap();
544 assert_eq!(json, r#""get""#);
545
546 let method = HttpMethod::POST;
547 let json = serde_json::to_string(&method).unwrap();
548 assert_eq!(json, r#""post""#);
549 }
550
551 #[test]
552 fn test_http_method_deserialization() {
553 let method: HttpMethod = serde_json::from_str(r#""get""#).unwrap();
554 assert_eq!(method, HttpMethod::GET);
555
556 let method: HttpMethod = serde_json::from_str(r#""post""#).unwrap();
557 assert_eq!(method, HttpMethod::POST);
558 }
559
560 #[test]
561 fn test_to_matchit_pattern() {
562 assert_eq!(to_matchit_pattern("/api/users"), "/api/users");
563 assert_eq!(to_matchit_pattern("/api/*/details"), "/api/{w2}/details");
564 assert_eq!(to_matchit_pattern("/api/*/*"), "/api/{w2}/{w3}");
565 assert_eq!(to_matchit_pattern("/*"), "/{w1}");
566 }
567
568 #[test]
569 fn test_matchit_many_routes_performance() {
570 let mut registry = RouteRegistry::new();
571
572 for i in 0..200 {
574 registry
575 .add_http_route(Route::new(HttpMethod::GET, format!("/api/v1/resource{i}")))
576 .unwrap();
577 }
578
579 let found = registry.find_http_routes(&HttpMethod::GET, "/api/v1/resource199");
581 assert_eq!(found.len(), 1);
582 assert_eq!(found[0].path, "/api/v1/resource199");
583
584 let found = registry.find_http_routes(&HttpMethod::GET, "/api/v1/resource999");
586 assert_eq!(found.len(), 0);
587 }
588}