elif_http/routing/
diagnostics.rs

1//! Route diagnostics and error reporting for development and debugging
2//!
3//! This module provides enhanced diagnostics for route conflicts, validation errors,
4//! and performance analysis to improve the developer experience.
5
6use crate::{
7    bootstrap::{RouteConflict, ConflictType, RouteInfo, ValidationReport},
8    routing::{HttpMethod, RouteDefinition},
9};
10use std::collections::HashMap;
11
12/// Enhanced diagnostics for route analysis and debugging
13#[derive(Debug)]
14pub struct RouteDiagnostics {
15    /// Enable colored output for terminal display
16    enable_colors: bool,
17    /// Enable detailed timing information
18    enable_timing: bool,
19    /// Maximum width for formatted output
20    max_width: usize,
21}
22
23impl RouteDiagnostics {
24    /// Create new route diagnostics instance
25    pub fn new() -> Self {
26        Self {
27            enable_colors: true,
28            enable_timing: false,
29            max_width: 80,
30        }
31    }
32
33    /// Configure color output
34    pub fn with_colors(mut self, enable: bool) -> Self {
35        self.enable_colors = enable;
36        self
37    }
38
39    /// Configure timing output
40    pub fn with_timing(mut self, enable: bool) -> Self {
41        self.enable_timing = enable;
42        self
43    }
44
45    /// Set maximum output width
46    pub fn with_max_width(mut self, width: usize) -> Self {
47        self.max_width = width;
48        self
49    }
50
51    /// Generate comprehensive conflict report
52    pub fn format_conflict_report(&self, conflicts: &[RouteConflict]) -> String {
53        let mut report = String::new();
54        
55        // Header
56        report.push_str(&self.format_header("Route Conflict Analysis"));
57        report.push('\n');
58
59        // Summary
60        report.push_str(&format!("Found {} route conflicts that need resolution:\n\n", conflicts.len()));
61
62        // Individual conflicts
63        for (i, conflict) in conflicts.iter().enumerate() {
64            if i > 0 {
65                report.push_str("\n");
66                report.push_str(&"─".repeat(self.max_width));
67                report.push_str("\n\n");
68            }
69
70            report.push_str(&self.format_conflict(conflict, i + 1));
71        }
72
73        // Footer with recommendations
74        report.push('\n');
75        report.push_str(&self.format_recommendations());
76
77        report
78    }
79
80    /// Format validation report summary
81    pub fn format_validation_summary(&self, report: &ValidationReport) -> String {
82        let mut output = String::new();
83
84        output.push_str(&self.format_header("Route Validation Summary"));
85        output.push('\n');
86
87        // Statistics
88        output.push_str(&format!("šŸ“Š Statistics:\n"));
89        output.push_str(&format!("   Total routes: {}\n", report.total_routes));
90        output.push_str(&format!("   Conflicts: {}\n", report.conflicts));
91        output.push_str(&format!("   Warnings: {}\n", report.warnings));
92        output.push_str(&format!("   Performance score: {}/100\n", report.performance_score));
93
94        if !report.suggestions.is_empty() {
95            output.push('\n');
96            output.push_str("šŸ’” Optimization Suggestions:\n");
97            for (i, suggestion) in report.suggestions.iter().enumerate() {
98                output.push_str(&format!("   {}. {}\n", i + 1, suggestion));
99            }
100        }
101
102        output
103    }
104
105    /// Format route analysis for debugging
106    pub fn format_route_analysis(&self, routes: &[RouteDefinition]) -> String {
107        let mut analysis = String::new();
108
109        analysis.push_str(&self.format_header("Route Analysis"));
110        analysis.push('\n');
111
112        // Group by HTTP method
113        let mut method_groups: HashMap<HttpMethod, Vec<&RouteDefinition>> = HashMap::new();
114        for route in routes {
115            method_groups.entry(route.method.clone()).or_default().push(route);
116        }
117
118        for (method, method_routes) in method_groups.iter() {
119            analysis.push_str(&format!("šŸ”— {} Routes ({})\n", method.as_str(), method_routes.len()));
120            
121            for route in method_routes {
122                let complexity = self.calculate_route_complexity(route);
123                let complexity_indicator = match complexity {
124                    0..=2 => "🟢",
125                    3..=5 => "🟔", 
126                    _ => "šŸ”“",
127                };
128                
129                analysis.push_str(&format!("   {} {} (complexity: {})\n", 
130                    complexity_indicator, route.path, complexity));
131            }
132            analysis.push('\n');
133        }
134
135        analysis
136    }
137
138    /// Format performance recommendations
139    pub fn format_performance_recommendations(&self, routes: &[RouteDefinition]) -> String {
140        let mut recommendations = String::new();
141        
142        recommendations.push_str(&self.format_header("Performance Recommendations"));
143        recommendations.push('\n');
144
145        // Analyze route complexity
146        let complex_routes: Vec<_> = routes.iter()
147            .filter(|r| self.calculate_route_complexity(r) > 5)
148            .collect();
149
150        if !complex_routes.is_empty() {
151            recommendations.push_str("āš ļø  Complex Routes Detected:\n");
152            for route in complex_routes {
153                recommendations.push_str(&format!("   • {} {} (consider simplifying)\n", 
154                    route.method.as_str(), route.path));
155            }
156            recommendations.push('\n');
157        }
158
159        // Route count recommendations
160        if routes.len() > 1000 {
161            recommendations.push_str("šŸ“ˆ Large Route Count:\n");
162            recommendations.push_str(&format!("   • {} routes detected\n", routes.len()));
163            recommendations.push_str("   • Consider route grouping or lazy loading\n");
164            recommendations.push_str("   • Review route organization patterns\n\n");
165        }
166
167        if recommendations.is_empty() {
168            recommendations.push_str("āœ… No performance issues detected\n");
169        }
170
171        recommendations
172    }
173
174    /// Format individual conflict details
175    fn format_conflict(&self, conflict: &RouteConflict, index: usize) -> String {
176        let mut output = String::new();
177
178        // Conflict header
179        let conflict_type_emoji = match conflict.conflict_type {
180            ConflictType::Exact => "🚨",
181            ConflictType::ParameterMismatch => "āš ļø",
182            ConflictType::Ambiguous => "ā“",
183            ConflictType::MiddlewareIncompatible => "šŸ”§",
184        };
185
186        output.push_str(&format!("{} Conflict #{}: {}\n\n", 
187            conflict_type_emoji, index, self.format_conflict_type(&conflict.conflict_type)));
188
189        // Route details
190        output.push_str("šŸ“ Conflicting Routes:\n");
191        output.push_str(&self.format_route_info(&conflict.route1, "1"));
192        output.push_str(&self.format_route_info(&conflict.route2, "2"));
193
194        // Resolution suggestions
195        if !conflict.resolution_suggestions.is_empty() {
196            output.push_str("\nšŸ’” Resolution Options:\n");
197            for (i, suggestion) in conflict.resolution_suggestions.iter().enumerate() {
198                output.push_str(&format!("   {}. {}\n", 
199                    i + 1, self.format_resolution_suggestion(suggestion)));
200            }
201        }
202
203        output
204    }
205
206    /// Format route information for display
207    fn format_route_info(&self, route: &RouteInfo, number: &str) -> String {
208        let mut info = String::new();
209        
210        info.push_str(&format!("   {}. {} {}\n", number, route.method.as_str(), route.path));
211        info.push_str(&format!("      Controller: {}::{}\n", route.controller, route.handler));
212        
213        if !route.middleware.is_empty() {
214            info.push_str(&format!("      Middleware: {:?}\n", route.middleware));
215        }
216        
217        if !route.parameters.is_empty() {
218            info.push_str("      Parameters: ");
219            let param_strs: Vec<String> = route.parameters.iter()
220                .map(|p| format!("{}: {}", p.name, p.param_type))
221                .collect();
222            info.push_str(&param_strs.join(", "));
223            info.push('\n');
224        }
225
226        info
227    }
228
229    /// Format conflict type description
230    fn format_conflict_type(&self, conflict_type: &ConflictType) -> String {
231        match conflict_type {
232            ConflictType::Exact => "Exact Route Duplicate".to_string(),
233            ConflictType::ParameterMismatch => "Parameter Type Mismatch".to_string(),
234            ConflictType::Ambiguous => "Ambiguous Route Pattern".to_string(),
235            ConflictType::MiddlewareIncompatible => "Middleware Incompatibility".to_string(),
236        }
237    }
238
239    /// Format resolution suggestion
240    fn format_resolution_suggestion(&self, suggestion: &crate::bootstrap::ConflictResolution) -> String {
241        use crate::bootstrap::ConflictResolution;
242        
243        match suggestion {
244            ConflictResolution::MergePaths { suggestion } => {
245                format!("Merge paths: {}", suggestion)
246            },
247            ConflictResolution::RenameParameter { from, to } => {
248                format!("Rename parameter '{}' to '{}'", from, to)
249            },
250            ConflictResolution::DifferentControllerPaths { suggestion } => {
251                format!("Use different controller paths: {}", suggestion)
252            },
253            ConflictResolution::MiddlewareConsolidation { suggestion } => {
254                format!("Consolidate middleware: {}", suggestion)
255            },
256            ConflictResolution::UseQueryParameters { suggestion } => {
257                format!("Use query parameters: {}", suggestion)
258            },
259            ConflictResolution::ReorderRoutes { suggestion } => {
260                format!("Reorder routes: {}", suggestion)
261            },
262        }
263    }
264
265    /// Format section header
266    fn format_header(&self, title: &str) -> String {
267        let border = "═".repeat(self.max_width);
268        format!("{}\n{:^width$}\n{}", border, title, border, width = self.max_width)
269    }
270
271    /// Format general recommendations
272    fn format_recommendations(&self) -> String {
273        let mut recommendations = String::new();
274        
275        recommendations.push_str("šŸŽÆ General Recommendations:\n");
276        recommendations.push_str("   • Use specific route patterns to avoid ambiguity\n");
277        recommendations.push_str("   • Group related routes in the same controller\n");
278        recommendations.push_str("   • Consistent parameter naming across controllers\n");
279        recommendations.push_str("   • Use middleware consistently for similar routes\n");
280        recommendations.push_str("   • Consider RESTful routing conventions\n");
281        
282        recommendations.push_str("\nšŸ“– Documentation:\n");
283        recommendations.push_str("   • Route conflict resolution: https://docs.elif.rs/routing/conflicts\n");
284        recommendations.push_str("   • Best practices: https://docs.elif.rs/routing/best-practices\n");
285
286        recommendations
287    }
288
289    /// Calculate route complexity score
290    fn calculate_route_complexity(&self, route: &RouteDefinition) -> u32 {
291        let mut complexity = 0;
292
293        // Count path segments
294        let segments = route.path.split('/').filter(|s| !s.is_empty()).count();
295        complexity += segments as u32;
296
297        // Count parameters
298        let param_count = route.path.matches('{').count();
299        complexity += param_count as u32 * 2; // Parameters add more complexity
300
301        // Bonus complexity for catch-all parameters
302        if route.path.contains("*") {
303            complexity += 3;
304        }
305
306        complexity
307    }
308}
309
310impl Default for RouteDiagnostics {
311    fn default() -> Self {
312        Self::new()
313    }
314}
315
316/// CLI-friendly formatter for route diagnostics
317pub struct CliDiagnosticsFormatter {
318    diagnostics: RouteDiagnostics,
319}
320
321impl CliDiagnosticsFormatter {
322    /// Create formatter optimized for CLI output
323    pub fn new() -> Self {
324        Self {
325            diagnostics: RouteDiagnostics::new()
326                .with_colors(true)
327                .with_max_width(120),
328        }
329    }
330
331    /// Create formatter for non-interactive environments
332    pub fn plain() -> Self {
333        Self {
334            diagnostics: RouteDiagnostics::new()
335                .with_colors(false)
336                .with_max_width(80),
337        }
338    }
339
340    /// Format conflicts for CLI display
341    pub fn format_conflicts(&self, conflicts: &[RouteConflict]) -> String {
342        self.diagnostics.format_conflict_report(conflicts)
343    }
344
345    /// Format validation summary for CLI
346    pub fn format_summary(&self, report: &ValidationReport) -> String {
347        self.diagnostics.format_validation_summary(report)
348    }
349}
350
351impl Default for CliDiagnosticsFormatter {
352    fn default() -> Self {
353        Self::new()
354    }
355}