use crate::{
bootstrap::{RouteConflict, ConflictType, RouteInfo, ValidationReport},
routing::{HttpMethod, RouteDefinition},
};
use std::collections::HashMap;
#[derive(Debug)]
pub struct RouteDiagnostics {
enable_colors: bool,
enable_timing: bool,
max_width: usize,
}
impl RouteDiagnostics {
pub fn new() -> Self {
Self {
enable_colors: true,
enable_timing: false,
max_width: 80,
}
}
pub fn with_colors(mut self, enable: bool) -> Self {
self.enable_colors = enable;
self
}
pub fn with_timing(mut self, enable: bool) -> Self {
self.enable_timing = enable;
self
}
pub fn with_max_width(mut self, width: usize) -> Self {
self.max_width = width;
self
}
pub fn format_conflict_report(&self, conflicts: &[RouteConflict]) -> String {
let mut report = String::new();
report.push_str(&self.format_header("Route Conflict Analysis"));
report.push('\n');
report.push_str(&format!("Found {} route conflicts that need resolution:\n\n", conflicts.len()));
for (i, conflict) in conflicts.iter().enumerate() {
if i > 0 {
report.push_str("\n");
report.push_str(&"─".repeat(self.max_width));
report.push_str("\n\n");
}
report.push_str(&self.format_conflict(conflict, i + 1));
}
report.push('\n');
report.push_str(&self.format_recommendations());
report
}
pub fn format_validation_summary(&self, report: &ValidationReport) -> String {
let mut output = String::new();
output.push_str(&self.format_header("Route Validation Summary"));
output.push('\n');
output.push_str(&format!("📊 Statistics:\n"));
output.push_str(&format!(" Total routes: {}\n", report.total_routes));
output.push_str(&format!(" Conflicts: {}\n", report.conflicts));
output.push_str(&format!(" Warnings: {}\n", report.warnings));
output.push_str(&format!(" Performance score: {}/100\n", report.performance_score));
if !report.suggestions.is_empty() {
output.push('\n');
output.push_str("💡 Optimization Suggestions:\n");
for (i, suggestion) in report.suggestions.iter().enumerate() {
output.push_str(&format!(" {}. {}\n", i + 1, suggestion));
}
}
output
}
pub fn format_route_analysis(&self, routes: &[RouteDefinition]) -> String {
let mut analysis = String::new();
analysis.push_str(&self.format_header("Route Analysis"));
analysis.push('\n');
let mut method_groups: HashMap<HttpMethod, Vec<&RouteDefinition>> = HashMap::new();
for route in routes {
method_groups.entry(route.method.clone()).or_default().push(route);
}
for (method, method_routes) in method_groups.iter() {
analysis.push_str(&format!("🔗 {} Routes ({})\n", method.as_str(), method_routes.len()));
for route in method_routes {
let complexity = self.calculate_route_complexity(route);
let complexity_indicator = match complexity {
0..=2 => "🟢",
3..=5 => "🟡",
_ => "🔴",
};
analysis.push_str(&format!(" {} {} (complexity: {})\n",
complexity_indicator, route.path, complexity));
}
analysis.push('\n');
}
analysis
}
pub fn format_performance_recommendations(&self, routes: &[RouteDefinition]) -> String {
let mut recommendations = String::new();
recommendations.push_str(&self.format_header("Performance Recommendations"));
recommendations.push('\n');
let complex_routes: Vec<_> = routes.iter()
.filter(|r| self.calculate_route_complexity(r) > 5)
.collect();
if !complex_routes.is_empty() {
recommendations.push_str("⚠️ Complex Routes Detected:\n");
for route in complex_routes {
recommendations.push_str(&format!(" • {} {} (consider simplifying)\n",
route.method.as_str(), route.path));
}
recommendations.push('\n');
}
if routes.len() > 1000 {
recommendations.push_str("📈 Large Route Count:\n");
recommendations.push_str(&format!(" • {} routes detected\n", routes.len()));
recommendations.push_str(" • Consider route grouping or lazy loading\n");
recommendations.push_str(" • Review route organization patterns\n\n");
}
if recommendations.is_empty() {
recommendations.push_str("✅ No performance issues detected\n");
}
recommendations
}
fn format_conflict(&self, conflict: &RouteConflict, index: usize) -> String {
let mut output = String::new();
let conflict_type_emoji = match conflict.conflict_type {
ConflictType::Exact => "🚨",
ConflictType::ParameterMismatch => "⚠️",
ConflictType::Ambiguous => "❓",
ConflictType::MiddlewareIncompatible => "🔧",
};
output.push_str(&format!("{} Conflict #{}: {}\n\n",
conflict_type_emoji, index, self.format_conflict_type(&conflict.conflict_type)));
output.push_str("📍 Conflicting Routes:\n");
output.push_str(&self.format_route_info(&conflict.route1, "1"));
output.push_str(&self.format_route_info(&conflict.route2, "2"));
if !conflict.resolution_suggestions.is_empty() {
output.push_str("\n💡 Resolution Options:\n");
for (i, suggestion) in conflict.resolution_suggestions.iter().enumerate() {
output.push_str(&format!(" {}. {}\n",
i + 1, self.format_resolution_suggestion(suggestion)));
}
}
output
}
fn format_route_info(&self, route: &RouteInfo, number: &str) -> String {
let mut info = String::new();
info.push_str(&format!(" {}. {} {}\n", number, route.method.as_str(), route.path));
info.push_str(&format!(" Controller: {}::{}\n", route.controller, route.handler));
if !route.middleware.is_empty() {
info.push_str(&format!(" Middleware: {:?}\n", route.middleware));
}
if !route.parameters.is_empty() {
info.push_str(" Parameters: ");
let param_strs: Vec<String> = route.parameters.iter()
.map(|p| format!("{}: {}", p.name, p.param_type))
.collect();
info.push_str(¶m_strs.join(", "));
info.push('\n');
}
info
}
fn format_conflict_type(&self, conflict_type: &ConflictType) -> String {
match conflict_type {
ConflictType::Exact => "Exact Route Duplicate".to_string(),
ConflictType::ParameterMismatch => "Parameter Type Mismatch".to_string(),
ConflictType::Ambiguous => "Ambiguous Route Pattern".to_string(),
ConflictType::MiddlewareIncompatible => "Middleware Incompatibility".to_string(),
}
}
fn format_resolution_suggestion(&self, suggestion: &crate::bootstrap::ConflictResolution) -> String {
use crate::bootstrap::ConflictResolution;
match suggestion {
ConflictResolution::MergePaths { suggestion } => {
format!("Merge paths: {}", suggestion)
},
ConflictResolution::RenameParameter { from, to } => {
format!("Rename parameter '{}' to '{}'", from, to)
},
ConflictResolution::DifferentControllerPaths { suggestion } => {
format!("Use different controller paths: {}", suggestion)
},
ConflictResolution::MiddlewareConsolidation { suggestion } => {
format!("Consolidate middleware: {}", suggestion)
},
ConflictResolution::UseQueryParameters { suggestion } => {
format!("Use query parameters: {}", suggestion)
},
ConflictResolution::ReorderRoutes { suggestion } => {
format!("Reorder routes: {}", suggestion)
},
}
}
fn format_header(&self, title: &str) -> String {
let border = "═".repeat(self.max_width);
format!("{}\n{:^width$}\n{}", border, title, border, width = self.max_width)
}
fn format_recommendations(&self) -> String {
let mut recommendations = String::new();
recommendations.push_str("🎯 General Recommendations:\n");
recommendations.push_str(" • Use specific route patterns to avoid ambiguity\n");
recommendations.push_str(" • Group related routes in the same controller\n");
recommendations.push_str(" • Consistent parameter naming across controllers\n");
recommendations.push_str(" • Use middleware consistently for similar routes\n");
recommendations.push_str(" • Consider RESTful routing conventions\n");
recommendations.push_str("\n📖 Documentation:\n");
recommendations.push_str(" • Route conflict resolution: https://docs.elif.rs/routing/conflicts\n");
recommendations.push_str(" • Best practices: https://docs.elif.rs/routing/best-practices\n");
recommendations
}
fn calculate_route_complexity(&self, route: &RouteDefinition) -> u32 {
let mut complexity = 0;
let segments = route.path.split('/').filter(|s| !s.is_empty()).count();
complexity += segments as u32;
let param_count = route.path.matches('{').count();
complexity += param_count as u32 * 2;
if route.path.contains("*") {
complexity += 3;
}
complexity
}
}
impl Default for RouteDiagnostics {
fn default() -> Self {
Self::new()
}
}
pub struct CliDiagnosticsFormatter {
diagnostics: RouteDiagnostics,
}
impl CliDiagnosticsFormatter {
pub fn new() -> Self {
Self {
diagnostics: RouteDiagnostics::new()
.with_colors(true)
.with_max_width(120),
}
}
pub fn plain() -> Self {
Self {
diagnostics: RouteDiagnostics::new()
.with_colors(false)
.with_max_width(80),
}
}
pub fn format_conflicts(&self, conflicts: &[RouteConflict]) -> String {
self.diagnostics.format_conflict_report(conflicts)
}
pub fn format_summary(&self, report: &ValidationReport) -> String {
self.diagnostics.format_validation_summary(report)
}
}
impl Default for CliDiagnosticsFormatter {
fn default() -> Self {
Self::new()
}
}