1use crate::{
7 bootstrap::{RouteConflict, ConflictType, RouteInfo, ValidationReport},
8 routing::{HttpMethod, RouteDefinition},
9};
10use std::collections::HashMap;
11
12#[derive(Debug)]
14pub struct RouteDiagnostics {
15 enable_colors: bool,
17 enable_timing: bool,
19 max_width: usize,
21}
22
23impl RouteDiagnostics {
24 pub fn new() -> Self {
26 Self {
27 enable_colors: true,
28 enable_timing: false,
29 max_width: 80,
30 }
31 }
32
33 pub fn with_colors(mut self, enable: bool) -> Self {
35 self.enable_colors = enable;
36 self
37 }
38
39 pub fn with_timing(mut self, enable: bool) -> Self {
41 self.enable_timing = enable;
42 self
43 }
44
45 pub fn with_max_width(mut self, width: usize) -> Self {
47 self.max_width = width;
48 self
49 }
50
51 pub fn format_conflict_report(&self, conflicts: &[RouteConflict]) -> String {
53 let mut report = String::new();
54
55 report.push_str(&self.format_header("Route Conflict Analysis"));
57 report.push('\n');
58
59 report.push_str(&format!("Found {} route conflicts that need resolution:\n\n", conflicts.len()));
61
62 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 report.push('\n');
75 report.push_str(&self.format_recommendations());
76
77 report
78 }
79
80 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 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 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 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 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 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 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 fn format_conflict(&self, conflict: &RouteConflict, index: usize) -> String {
176 let mut output = String::new();
177
178 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 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 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 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(¶m_strs.join(", "));
223 info.push('\n');
224 }
225
226 info
227 }
228
229 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 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 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 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 fn calculate_route_complexity(&self, route: &RouteDefinition) -> u32 {
291 let mut complexity = 0;
292
293 let segments = route.path.split('/').filter(|s| !s.is_empty()).count();
295 complexity += segments as u32;
296
297 let param_count = route.path.matches('{').count();
299 complexity += param_count as u32 * 2; 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
316pub struct CliDiagnosticsFormatter {
318 diagnostics: RouteDiagnostics,
319}
320
321impl CliDiagnosticsFormatter {
322 pub fn new() -> Self {
324 Self {
325 diagnostics: RouteDiagnostics::new()
326 .with_colors(true)
327 .with_max_width(120),
328 }
329 }
330
331 pub fn plain() -> Self {
333 Self {
334 diagnostics: RouteDiagnostics::new()
335 .with_colors(false)
336 .with_max_width(80),
337 }
338 }
339
340 pub fn format_conflicts(&self, conflicts: &[RouteConflict]) -> String {
342 self.diagnostics.format_conflict_report(conflicts)
343 }
344
345 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}