1use crate::{
8 bootstrap::BootstrapError,
9 routing::{HttpMethod, RouteMatchError, RouteMatcher, RouteDefinition},
10};
11use std::collections::{HashMap, HashSet};
12use thiserror::Error;
13
14#[derive(Error, Debug)]
16pub enum RouteValidationError {
17 #[error("Route conflict detected")]
18 ConflictDetected {
19 conflicts: Vec<RouteConflict>,
20 },
21 #[error("Parameter type conflict in route {route}: {details}")]
22 ParameterConflict {
23 route: String,
24 details: String,
25 },
26 #[error("Invalid route configuration: {message}")]
27 InvalidConfiguration {
28 message: String,
29 },
30 #[error("Route validation failed: {0}")]
31 ValidationFailed(#[from] RouteMatchError),
32}
33
34#[derive(Debug, Clone)]
36pub struct RouteConflict {
37 pub route1: RouteInfo,
38 pub route2: RouteInfo,
39 pub conflict_type: ConflictType,
40 pub resolution_suggestions: Vec<ConflictResolution>,
41}
42
43#[derive(Debug, Clone)]
45pub struct RouteInfo {
46 pub method: HttpMethod,
47 pub path: String,
48 pub controller: String,
49 pub handler: String,
50 pub middleware: Vec<String>,
51 pub parameters: Vec<ParamDef>,
52}
53
54#[derive(Debug, Clone)]
56pub enum ConflictType {
57 Exact,
59 ParameterMismatch,
61 Ambiguous,
63 MiddlewareIncompatible,
65}
66
67#[derive(Debug, Clone)]
69pub struct ParamDef {
70 pub name: String,
71 pub param_type: String,
72 pub required: bool,
73 pub constraints: Vec<String>,
74}
75
76#[derive(Debug, Clone)]
78pub enum ConflictResolution {
79 MergePaths { suggestion: String },
80 RenameParameter { from: String, to: String },
81 DifferentControllerPaths { suggestion: String },
82 MiddlewareConsolidation { suggestion: String },
83 UseQueryParameters { suggestion: String },
84 ReorderRoutes { suggestion: String },
85}
86
87#[derive(Debug)]
89pub struct RouteValidator {
90 routes: HashMap<RouteKey, RouteRegistration>,
92 matcher: RouteMatcher,
94 enable_diagnostics: bool,
96}
97
98#[derive(Hash, PartialEq, Eq, Debug, Clone)]
100pub struct RouteKey {
101 pub method: HttpMethod,
102 pub path_pattern: String,
103}
104
105#[derive(Debug, Clone)]
107pub struct RouteRegistration {
108 pub controller: String,
109 pub handler: String,
110 pub middleware: Vec<String>,
111 pub parameters: Vec<ParamDef>,
112 pub definition: RouteDefinition,
113}
114
115impl RouteValidator {
116 pub fn new() -> Self {
118 Self {
119 routes: HashMap::new(),
120 matcher: RouteMatcher::new(),
121 enable_diagnostics: true,
122 }
123 }
124
125 pub fn with_diagnostics(mut self, enable: bool) -> Self {
127 self.enable_diagnostics = enable;
128 self
129 }
130
131 pub fn register_route(&mut self, registration: RouteRegistration) -> Result<(), RouteValidationError> {
133 let route_key = RouteKey {
134 method: registration.definition.method.clone(),
135 path_pattern: registration.definition.path.clone(),
136 };
137
138 if let Some(existing) = self.routes.get(&route_key) {
140 let conflict = self.analyze_conflict(®istration, existing)?;
141 return Err(RouteValidationError::ConflictDetected {
142 conflicts: vec![conflict],
143 });
144 }
145
146 self.matcher.add_route(registration.definition.clone())
148 .map_err(RouteValidationError::ValidationFailed)?;
149
150 self.routes.insert(route_key, registration);
152
153 Ok(())
154 }
155
156 pub fn validate_all_routes(&self) -> Result<ValidationReport, RouteValidationError> {
158 let mut conflicts = Vec::new();
159 let mut warnings = Vec::new();
160
161 self.check_parameter_conflicts(&mut conflicts);
163
164 self.check_middleware_conflicts(&mut warnings);
166
167 self.check_performance_issues(&mut warnings);
169
170 if !conflicts.is_empty() {
171 return Err(RouteValidationError::ConflictDetected { conflicts });
172 }
173
174 Ok(ValidationReport {
175 total_routes: self.routes.len(),
176 conflicts: conflicts.len(),
177 warnings: warnings.len(),
178 performance_score: self.calculate_performance_score(),
179 suggestions: self.generate_optimization_suggestions(),
180 })
181 }
182
183 pub fn generate_conflict_report(&self, conflicts: &[RouteConflict]) -> String {
185 let mut report = String::new();
186
187 for (i, conflict) in conflicts.iter().enumerate() {
188 if i > 0 {
189 report.push_str("\n\n");
190 }
191
192 match conflict.conflict_type {
193 ConflictType::Exact => {
194 report.push_str(&format!(
195 "Error: Duplicate route definition detected\n\n\
196 Route: {} {}\n\
197 Defined in:\n\
198 1. {}::{}\n\
199 2. {}::{}\n\n\
200 Resolution suggestions:",
201 conflict.route1.method.as_str(),
202 conflict.route1.path,
203 conflict.route1.controller,
204 conflict.route1.handler,
205 conflict.route2.controller,
206 conflict.route2.handler
207 ));
208 }
209 ConflictType::ParameterMismatch => {
210 report.push_str(&format!(
211 "Error: Route parameter type conflict\n\n\
212 Route pattern: {} {}\n\
213 Parameter conflicts:\n\
214 • {} expects different types\n\
215 • {} expects different types\n\n\
216 Resolution: Ensure all controllers use the same parameter types",
217 conflict.route1.method.as_str(),
218 conflict.route1.path,
219 conflict.route1.controller,
220 conflict.route2.controller
221 ));
222 }
223 ConflictType::Ambiguous => {
224 report.push_str(&format!(
225 "Error: Ambiguous route patterns detected\n\n\
226 Routes that could match the same request:\n\
227 1. {} {} ({})\n\
228 2. {} {} ({})\n\n\
229 Problem: These patterns could match the same request\n\n\
230 Resolution: Reorder routes or use more specific patterns",
231 conflict.route1.method.as_str(),
232 conflict.route1.path,
233 conflict.route1.controller,
234 conflict.route2.method.as_str(),
235 conflict.route2.path,
236 conflict.route2.controller
237 ));
238 }
239 ConflictType::MiddlewareIncompatible => {
240 report.push_str(&format!(
241 "Warning: Middleware incompatibility detected\n\n\
242 Route: {} {}\n\
243 Controllers with different middleware:\n\
244 • {}: {:?}\n\
245 • {}: {:?}\n\n\
246 Resolution: Consider consolidating middleware requirements",
247 conflict.route1.method.as_str(),
248 conflict.route1.path,
249 conflict.route1.controller,
250 conflict.route1.middleware,
251 conflict.route2.controller,
252 conflict.route2.middleware
253 ));
254 }
255 }
256
257 for (j, suggestion) in conflict.resolution_suggestions.iter().enumerate() {
259 report.push_str(&format!("\n {}. {}", j + 1, self.format_suggestion(suggestion)));
260 }
261 }
262
263 report
264 }
265
266 fn analyze_conflict(&self, route1: &RouteRegistration, route2: &RouteRegistration) -> Result<RouteConflict, RouteValidationError> {
268 let route_info1 = RouteInfo {
269 method: route1.definition.method.clone(),
270 path: route1.definition.path.clone(),
271 controller: route1.controller.clone(),
272 handler: route1.handler.clone(),
273 middleware: route1.middleware.clone(),
274 parameters: route1.parameters.clone(),
275 };
276
277 let route_info2 = RouteInfo {
278 method: route2.definition.method.clone(),
279 path: route2.definition.path.clone(),
280 controller: route2.controller.clone(),
281 handler: route2.handler.clone(),
282 middleware: route2.middleware.clone(),
283 parameters: route2.parameters.clone(),
284 };
285
286 let conflict_type = if route1.definition.path == route2.definition.path {
287 if self.parameters_conflict(&route1.parameters, &route2.parameters) {
288 ConflictType::ParameterMismatch
289 } else {
290 ConflictType::Exact
291 }
292 } else {
293 ConflictType::Ambiguous
294 };
295
296 let resolution_suggestions = self.generate_resolution_suggestions(&route_info1, &route_info2, &conflict_type);
297
298 Ok(RouteConflict {
299 route1: route_info1,
300 route2: route_info2,
301 conflict_type,
302 resolution_suggestions,
303 })
304 }
305
306 fn parameters_conflict(&self, params1: &[ParamDef], params2: &[ParamDef]) -> bool {
308 for param1 in params1 {
309 for param2 in params2 {
310 if param1.name == param2.name && param1.param_type != param2.param_type {
311 return true;
312 }
313 }
314 }
315 false
316 }
317
318 fn check_parameter_conflicts(&self, _conflicts: &mut Vec<RouteConflict>) {
320 let mut param_types: HashMap<String, (String, String)> = HashMap::new();
321
322 for registration in self.routes.values() {
323 for param in ®istration.parameters {
324 let key = format!("{}:{}", registration.definition.path, param.name);
325 if let Some((existing_type, _existing_controller)) = param_types.get(&key) {
326 if existing_type != ¶m.param_type {
327 }
330 } else {
331 param_types.insert(key, (param.param_type.clone(), registration.controller.clone()));
332 }
333 }
334 }
335 }
336
337 fn check_middleware_conflicts(&self, warnings: &mut Vec<String>) {
339 let mut path_middleware: HashMap<String, Vec<(String, Vec<String>)>> = HashMap::new();
341
342 for registration in self.routes.values() {
343 let path = ®istration.definition.path;
344 path_middleware
345 .entry(path.clone())
346 .or_default()
347 .push((registration.controller.clone(), registration.middleware.clone()));
348 }
349
350 for (path, controllers) in path_middleware {
351 if controllers.len() > 1 {
352 let middleware_sets: HashSet<Vec<String>> = controllers.iter().map(|(_, mw)| mw.clone()).collect();
353 if middleware_sets.len() > 1 {
354 warnings.push(format!(
355 "Inconsistent middleware for path {}: controllers have different middleware requirements",
356 path
357 ));
358 }
359 }
360 }
361 }
362
363 fn check_performance_issues(&self, warnings: &mut Vec<String>) {
365 if self.routes.len() > 1000 {
366 warnings.push("Large number of routes (>1000) may impact performance".to_string());
367 }
368
369 for registration in self.routes.values() {
371 let param_count = registration.parameters.len();
372 if param_count > 5 {
373 warnings.push(format!(
374 "Route {} has {} parameters, consider simplifying",
375 registration.definition.path,
376 param_count
377 ));
378 }
379 }
380 }
381
382 fn calculate_performance_score(&self) -> u32 {
384 let base_score: u32 = 100;
385 let route_penalty = (self.routes.len() / 100) as u32; let complex_routes = self.routes.values()
388 .filter(|r| r.parameters.len() > 3)
389 .count() as u32;
390
391 base_score.saturating_sub(route_penalty + complex_routes)
392 }
393
394 fn generate_optimization_suggestions(&self) -> Vec<String> {
396 let mut suggestions = Vec::new();
397
398 if self.routes.len() > 500 {
399 suggestions.push("Consider grouping routes by modules for better organization".to_string());
400 }
401
402 suggestions
403 }
404
405 fn generate_resolution_suggestions(&self, _route1: &RouteInfo, _route2: &RouteInfo, conflict_type: &ConflictType) -> Vec<ConflictResolution> {
407 match conflict_type {
408 ConflictType::Exact => vec![
409 ConflictResolution::DifferentControllerPaths {
410 suggestion: "Use different base paths like /api/users vs /api/admin/users".to_string()
411 },
412 ConflictResolution::MergePaths {
413 suggestion: "Merge functionality into a single controller".to_string()
414 },
415 ConflictResolution::UseQueryParameters {
416 suggestion: "Use query parameters instead: GET /api/users/{id}?admin=true".to_string()
417 },
418 ],
419 ConflictType::ParameterMismatch => vec![
420 ConflictResolution::RenameParameter {
421 from: "id".to_string(),
422 to: "user_id".to_string()
423 },
424 ],
425 ConflictType::Ambiguous => vec![
426 ConflictResolution::ReorderRoutes {
427 suggestion: "Reorder routes to put more specific patterns first".to_string()
428 },
429 ],
430 ConflictType::MiddlewareIncompatible => vec![
431 ConflictResolution::MiddlewareConsolidation {
432 suggestion: "Consolidate middleware requirements across controllers".to_string()
433 },
434 ],
435 }
436 }
437
438 fn format_suggestion(&self, suggestion: &ConflictResolution) -> String {
440 match suggestion {
441 ConflictResolution::MergePaths { suggestion } => suggestion.clone(),
442 ConflictResolution::RenameParameter { from, to } => {
443 format!("Rename parameter '{}' to '{}'", from, to)
444 },
445 ConflictResolution::DifferentControllerPaths { suggestion } => suggestion.clone(),
446 ConflictResolution::MiddlewareConsolidation { suggestion } => suggestion.clone(),
447 ConflictResolution::UseQueryParameters { suggestion } => suggestion.clone(),
448 ConflictResolution::ReorderRoutes { suggestion } => suggestion.clone(),
449 }
450 }
451}
452
453#[derive(Debug)]
455pub struct ValidationReport {
456 pub total_routes: usize,
457 pub conflicts: usize,
458 pub warnings: usize,
459 pub performance_score: u32,
460 pub suggestions: Vec<String>,
461}
462
463impl Default for RouteValidator {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469impl From<RouteValidationError> for BootstrapError {
471 fn from(err: RouteValidationError) -> Self {
472 BootstrapError::RouteRegistrationFailed {
473 message: format!("Route validation failed: {}", err),
474 }
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 fn create_test_route(
484 controller: &str,
485 handler: &str,
486 method: HttpMethod,
487 path: &str,
488 params: Vec<ParamDef>,
489 ) -> RouteRegistration {
490 RouteRegistration {
491 controller: controller.to_string(),
492 handler: handler.to_string(),
493 middleware: Vec::new(),
494 parameters: params,
495 definition: RouteDefinition {
496 id: format!("{}::{}", controller, handler),
497 method,
498 path: path.to_string(),
499 },
500 }
501 }
502
503 #[test]
504 fn test_successful_route_registration() {
505 let mut validator = RouteValidator::new();
506
507 let route = create_test_route(
508 "UserController",
509 "get_user",
510 HttpMethod::GET,
511 "/api/users/{id}",
512 vec![ParamDef {
513 name: "id".to_string(),
514 param_type: "u32".to_string(),
515 required: true,
516 constraints: vec!["int".to_string()],
517 }]
518 );
519
520 let result = validator.register_route(route);
521 assert!(result.is_ok(), "Route registration should succeed");
522
523 let report = validator.validate_all_routes().unwrap();
524 assert_eq!(report.total_routes, 1);
525 assert_eq!(report.conflicts, 0);
526 }
527
528 #[test]
529 fn test_exact_route_conflict_detection() {
530 let mut validator = RouteValidator::new();
531
532 let route1 = create_test_route(
534 "UserController",
535 "get_user",
536 HttpMethod::GET,
537 "/api/users/{id}",
538 vec![ParamDef {
539 name: "id".to_string(),
540 param_type: "u32".to_string(),
541 required: true,
542 constraints: vec!["int".to_string()],
543 }]
544 );
545 validator.register_route(route1).unwrap();
546
547 let route2 = create_test_route(
549 "AdminController",
550 "get_admin_user",
551 HttpMethod::GET,
552 "/api/users/{id}", vec![ParamDef {
554 name: "id".to_string(),
555 param_type: "u32".to_string(),
556 required: true,
557 constraints: vec!["int".to_string()],
558 }]
559 );
560
561 let result = validator.register_route(route2);
562 assert!(result.is_err(), "Conflicting route should be rejected");
563
564 match result.unwrap_err() {
565 RouteValidationError::ConflictDetected { conflicts } => {
566 assert_eq!(conflicts.len(), 1);
567 assert!(matches!(conflicts[0].conflict_type, ConflictType::Exact));
568 },
569 _ => panic!("Expected ConflictDetected error"),
570 }
571 }
572
573 #[test]
574 fn test_parameter_conflict_detection() {
575 let validator = RouteValidator::new();
576
577 let route1 = create_test_route(
578 "UserController",
579 "get_user",
580 HttpMethod::GET,
581 "/api/users/{id}",
582 vec![ParamDef {
583 name: "id".to_string(),
584 param_type: "u32".to_string(),
585 required: true,
586 constraints: vec!["int".to_string()],
587 }]
588 );
589
590 let route2 = create_test_route(
591 "AdminController",
592 "get_admin_user",
593 HttpMethod::GET,
594 "/api/users/{id}",
595 vec![ParamDef {
596 name: "id".to_string(),
597 param_type: "String".to_string(), required: true,
599 constraints: vec!["string".to_string()],
600 }]
601 );
602
603 let conflicts = validator.parameters_conflict(&route1.parameters, &route2.parameters);
605 assert!(conflicts, "Parameters with same name but different types should conflict");
606 }
607
608 #[test]
609 fn test_conflict_report_generation() {
610 let validator = RouteValidator::new();
611
612 let route_info1 = RouteInfo {
613 method: HttpMethod::GET,
614 path: "/api/users/{id}".to_string(),
615 controller: "UserController".to_string(),
616 handler: "get_user".to_string(),
617 middleware: Vec::new(),
618 parameters: Vec::new(),
619 };
620
621 let route_info2 = RouteInfo {
622 method: HttpMethod::GET,
623 path: "/api/users/{id}".to_string(),
624 controller: "AdminController".to_string(),
625 handler: "get_admin_user".to_string(),
626 middleware: Vec::new(),
627 parameters: Vec::new(),
628 };
629
630 let conflict = RouteConflict {
631 route1: route_info1,
632 route2: route_info2,
633 conflict_type: ConflictType::Exact,
634 resolution_suggestions: vec![
635 ConflictResolution::DifferentControllerPaths {
636 suggestion: "Use different paths".to_string()
637 }
638 ],
639 };
640
641 let report = validator.generate_conflict_report(&[conflict]);
642
643 assert!(report.contains("Duplicate route definition detected"));
644 assert!(report.contains("UserController::get_user"));
645 assert!(report.contains("AdminController::get_admin_user"));
646 assert!(report.contains("Resolution suggestions"));
647 }
648
649 #[test]
650 fn test_validation_report_generation() {
651 let mut validator = RouteValidator::new();
652
653 for i in 0..5 {
655 let route = create_test_route(
656 &format!("Controller{}", i),
657 "handler",
658 HttpMethod::GET,
659 &format!("/api/resource{}/{}", i, "{id}"),
660 vec![ParamDef {
661 name: "id".to_string(),
662 param_type: "u32".to_string(),
663 required: true,
664 constraints: vec!["int".to_string()],
665 }]
666 );
667 validator.register_route(route).unwrap();
668 }
669
670 let report = validator.validate_all_routes().unwrap();
671
672 assert_eq!(report.total_routes, 5);
673 assert_eq!(report.conflicts, 0);
674 assert!(report.performance_score > 0);
675 }
676
677 #[test]
678 fn test_performance_scoring() {
679 let validator = RouteValidator::new();
680
681 let score = validator.calculate_performance_score();
683 assert_eq!(score, 100);
684 }
685
686 #[test]
687 fn test_resolution_suggestions() {
688 let validator = RouteValidator::new();
689
690 let route1 = RouteInfo {
691 method: HttpMethod::GET,
692 path: "/api/users".to_string(),
693 controller: "UserController".to_string(),
694 handler: "list".to_string(),
695 middleware: Vec::new(),
696 parameters: Vec::new(),
697 };
698
699 let route2 = RouteInfo {
700 method: HttpMethod::GET,
701 path: "/api/users".to_string(),
702 controller: "AdminController".to_string(),
703 handler: "list_admin".to_string(),
704 middleware: Vec::new(),
705 parameters: Vec::new(),
706 };
707
708 let suggestions = validator.generate_resolution_suggestions(&route1, &route2, &ConflictType::Exact);
709
710 assert!(!suggestions.is_empty());
711 assert!(matches!(suggestions[0], ConflictResolution::DifferentControllerPaths { .. }));
712 }
713}