Skip to main content

cargo_mate/tools/
error_derive.rs

1use super::{Tool, Result, ToolError, common_options, parse_output_format, OutputFormat};
2use clap::{Arg, ArgMatches, Command};
3use colored::*;
4use std::fs;
5use std::path::Path;
6use syn::{parse_file, File, Item, ItemFn, ItemImpl, ReturnType, Type, visit::Visit};
7use quote::quote;
8use proc_macro2::TokenStream;
9#[derive(Debug, Clone)]
10pub struct ErrorDeriveTool;
11#[derive(Debug, Clone)]
12struct ErrorPattern {
13    error_type: String,
14    context: Vec<String>,
15    usage_count: usize,
16}
17#[derive(Debug, Clone)]
18struct ErrorAnalysis {
19    patterns: Vec<ErrorPattern>,
20    error_types: Vec<String>,
21    error_handling_patterns: Vec<String>,
22}
23impl ErrorDeriveTool {
24    pub fn new() -> Self {
25        Self
26    }
27    fn analyze_error_usage(&self, file_path: &str) -> Result<ErrorAnalysis> {
28        let content = fs::read_to_string(file_path)
29            .map_err(|e| ToolError::ExecutionFailed(
30                format!("Failed to read {}: {}", file_path, e),
31            ))?;
32        let ast = parse_file(&content)
33            .map_err(|e| ToolError::ExecutionFailed(
34                format!("Failed to parse {}: {}", file_path, e),
35            ))?;
36        let mut visitor = ErrorUsageVisitor::new();
37        visitor.visit_file(&ast);
38        Ok(ErrorAnalysis {
39            patterns: visitor.error_patterns,
40            error_types: visitor.error_types,
41            error_handling_patterns: visitor.error_handling_patterns,
42        })
43    }
44    fn analyze_directory(&self, dir_path: &str) -> Result<ErrorAnalysis> {
45        let mut all_patterns = Vec::new();
46        let mut all_error_types = Vec::new();
47        let mut all_handling_patterns = Vec::new();
48        fn visit_dir(
49            dir: &Path,
50            patterns: &mut Vec<ErrorPattern>,
51            error_types: &mut Vec<String>,
52            handling_patterns: &mut Vec<String>,
53        ) -> Result<()> {
54            let entries = fs::read_dir(dir)
55                .map_err(|e| ToolError::ExecutionFailed(
56                    format!("Failed to read directory: {}", e),
57                ))?;
58            for entry in entries {
59                let entry = entry
60                    .map_err(|e| ToolError::ExecutionFailed(
61                        format!("Failed to read entry: {}", e),
62                    ))?;
63                let path = entry.path();
64                if path.is_dir() {
65                    visit_dir(&path, patterns, error_types, handling_patterns)?;
66                } else if let Some(ext) = path.extension() {
67                    if ext == "rs" && path.file_name().unwrap_or_default() != "mod.rs" {
68                        if let Ok(analysis) = ErrorDeriveTool::new()
69                            .analyze_error_usage(&path.to_string_lossy())
70                        {
71                            patterns.extend(analysis.patterns);
72                            error_types.extend(analysis.error_types);
73                            handling_patterns.extend(analysis.error_handling_patterns);
74                        }
75                    }
76                }
77            }
78            Ok(())
79        }
80        visit_dir(
81            Path::new(dir_path),
82            &mut all_patterns,
83            &mut all_error_types,
84            &mut all_handling_patterns,
85        )?;
86        let mut consolidated_patterns = Vec::new();
87        let mut pattern_map: std::collections::HashMap<String, ErrorPattern> = std::collections::HashMap::new();
88        for pattern in all_patterns {
89            if let Some(existing) = pattern_map.get_mut(&pattern.error_type) {
90                existing.usage_count += pattern.usage_count;
91                existing.context.extend(pattern.context);
92            } else {
93                pattern_map.insert(pattern.error_type.clone(), pattern);
94            }
95        }
96        consolidated_patterns.extend(pattern_map.values().cloned());
97        Ok(ErrorAnalysis {
98            patterns: consolidated_patterns,
99            error_types: all_error_types
100                .into_iter()
101                .collect::<std::collections::HashSet<_>>()
102                .into_iter()
103                .collect(),
104            error_handling_patterns: all_handling_patterns
105                .into_iter()
106                .collect::<std::collections::HashSet<_>>()
107                .into_iter()
108                .collect(),
109        })
110    }
111    fn generate_error_enum(
112        &self,
113        analysis: &ErrorAnalysis,
114        name: &str,
115    ) -> Result<String> {
116        let mut code = format!(
117            "// Generated error types based on codebase analysis\n\n"
118        );
119        code.push_str("use std::fmt;\n");
120        code.push_str("use thiserror::Error;\n\n");
121        code.push_str("#[derive(Debug, Error)]\n");
122        code.push_str(&format!("pub enum {} {{\n", name));
123        for error_type in &analysis.error_types {
124            let variant_name = self.error_type_to_variant_name(error_type);
125            let display_msg = self.generate_display_message(error_type);
126            code.push_str("    #[error(\"");
127            code.push_str(&display_msg);
128            code.push_str("\")]\n");
129            code.push_str(&format!("    {}({}),\n", variant_name, error_type));
130        }
131        code.push_str("    #[error(\"Validation failed: {field} - {message}\")]\n");
132        code.push_str("    Validation { field: String, message: String },\n\n");
133        code.push_str("    #[error(\"Configuration error: {message}\")]\n");
134        code.push_str("    Config { message: String },\n\n");
135        code.push_str("    #[error(\"Unknown error: {0}\")]\n");
136        code.push_str("    Unknown(String),\n");
137        code.push_str("}\n\n");
138        for error_type in &analysis.error_types {
139            code.push_str(&format!("impl From<{}> for {} {{\n", error_type, name));
140            code.push_str(&format!("    fn from(err: {}) -> Self {{\n", error_type));
141            let variant_name = self.error_type_to_variant_name(error_type);
142            code.push_str(&format!("        {}::{}(err)\n", name, variant_name));
143            code.push_str("    }\n");
144            code.push_str("}\n\n");
145        }
146        Ok(code)
147    }
148    fn generate_error_impls(&self, error_name: &str) -> Result<String> {
149        let mut code = format!("// Error implementation helpers for {}\n\n", error_name);
150        code.push_str("use std::fmt;\n\n");
151        code.push_str(&format!("impl {} {{\n", error_name));
152        code.push_str(
153            "    pub fn with_context<S: Into<String>>(self, context: S) -> Self {\n",
154        );
155        code.push_str("        match self {\n");
156        code.push_str("            Self::Validation { field, message } => {\n");
157        code.push_str("                Self::Validation {\n");
158        code.push_str("                    field,\n");
159        code.push_str(
160            "                    message: format!(\"{}: {}\", context.into(), message),\n",
161        );
162        code.push_str("                }\n");
163        code.push_str("            }\n");
164        code.push_str("            Self::Config { message } => {\n");
165        code.push_str("                Self::Config {\n");
166        code.push_str(
167            "                    message: format!(\"{}: {}\", context.into(), message),\n",
168        );
169        code.push_str("                }\n");
170        code.push_str("            }\n");
171        code.push_str("            Self::Unknown(msg) => {\n");
172        code.push_str(
173            "                Self::Unknown(format!(\"{}: {}\", context.into(), msg))\n",
174        );
175        code.push_str("            }\n");
176        code.push_str("            other => other,\n");
177        code.push_str("        }\n");
178        code.push_str("    }\n\n");
179        code.push_str(
180            "    pub fn with_field<S: Into<String>>(self, field: S) -> Self {\n",
181        );
182        code.push_str("        Self::Validation {\n");
183        code.push_str("            field: field.into(),\n");
184        code.push_str("            message: self.to_string(),\n");
185        code.push_str("        }\n");
186        code.push_str("    }\n\n");
187        code.push_str(
188            "    pub fn validation<S: Into<String>>(field: S, message: S) -> Self {\n",
189        );
190        code.push_str("        Self::Validation {\n");
191        code.push_str("            field: field.into(),\n");
192        code.push_str("            message: message.into(),\n");
193        code.push_str("        }\n");
194        code.push_str("    }\n\n");
195        code.push_str("    pub fn config<S: Into<String>>(message: S) -> Self {\n");
196        code.push_str("        Self::Config {\n");
197        code.push_str("            message: message.into(),\n");
198        code.push_str("        }\n");
199        code.push_str("    }\n\n");
200        code.push_str("    pub fn unknown<S: Into<String>>(message: S) -> Self {\n");
201        code.push_str("        Self::Unknown(message.into())\n");
202        code.push_str("    }\n");
203        code.push_str("}\n\n");
204        Ok(code)
205    }
206    fn generate_context_methods(&self, error_name: &str) -> Result<String> {
207        let mut code = format!("// Context helper methods for {}\n\n", error_name);
208        code.push_str("use std::fmt;\n\n");
209        code.push_str(
210            &format!("pub type {}Result<T> = Result<T, {}>;\n\n", error_name, error_name),
211        );
212        code.push_str("/// Add context to a Result\n");
213        code.push_str(
214            &format!(
215                "pub fn with_context<T, E, S>(result: Result<T, E>, context: S) -> Result<T, {}>\n",
216                error_name
217            ),
218        );
219        code.push_str("where\n");
220        code.push_str("    E: fmt::Display,\n");
221        code.push_str("    S: Into<String>,\n");
222        code.push_str("{\n");
223        code.push_str("    result.map_err(|err| {\n");
224        code.push_str(
225            &format!(
226                "        {}::unknown(format!(\"{{}}: {{}}\", context.into(), err))\n",
227                error_name
228            ),
229        );
230        code.push_str("    })\n");
231        code.push_str("}\n\n");
232        code.push_str("/// Add field context to a validation error\n");
233        code.push_str(
234            &format!(
235                "pub fn with_field<T, S>(field: S) -> impl FnOnce(T) -> {}\n", error_name
236            ),
237        );
238        code.push_str("where\n");
239        code.push_str("    T: fmt::Display,\n");
240        code.push_str("    S: Into<String>,\n");
241        code.push_str("{\n");
242        code.push_str("    let field = field.into();\n");
243        code.push_str("    move |err| {\n");
244        code.push_str(
245            &format!("        {}::validation(field, err.to_string())\n", error_name),
246        );
247        code.push_str("    }\n");
248        code.push_str("}\n\n");
249        code.push_str("/// Macro for creating validation errors\n");
250        code.push_str("#[macro_export]\n");
251        code.push_str(
252            &format!("macro_rules! {}validation_error {{\n", error_name.to_lowercase()),
253        );
254        code.push_str("    ($field:expr, $msg:expr) => {\n");
255        code.push_str(&format!("        {}::validation($field, $msg)\n", error_name));
256        code.push_str("    };\n");
257        code.push_str("}\n\n");
258        code.push_str("/// Macro for adding context to errors\n");
259        code.push_str("#[macro_export]\n");
260        code.push_str(
261            &format!("macro_rules! {}with_context {{\n", error_name.to_lowercase()),
262        );
263        code.push_str("    ($result:expr, $context:expr) => {\n");
264        code.push_str("        $result.map_err(|err| {\n");
265        code.push_str(
266            &format!(
267                "            {}::unknown(format!(\"{{}}: {{}}\", $context, err))\n",
268                error_name
269            ),
270        );
271        code.push_str("        })\n");
272        code.push_str("    };\n");
273        code.push_str("}\n\n");
274        Ok(code)
275    }
276    fn generate_comprehensive_error_type(
277        &self,
278        analysis: &ErrorAnalysis,
279        name: &str,
280    ) -> Result<String> {
281        let mut code = String::new();
282        code.push_str(&self.generate_error_enum(analysis, name)?);
283        code.push_str("\n");
284        code.push_str(&self.generate_error_impls(name)?);
285        code.push_str("\n");
286        code.push_str(&self.generate_context_methods(name)?);
287        Ok(code)
288    }
289    fn error_type_to_variant_name(&self, error_type: &str) -> String {
290        error_type
291            .replace("::", "_")
292            .replace("<", "_")
293            .replace(">", "_")
294            .replace(",", "_")
295            .replace(" ", "_")
296            .replace("(", "")
297            .replace(")", "")
298            .replace("[", "")
299            .replace("]", "")
300            .replace("'", "")
301            .replace("\"", "")
302    }
303    fn generate_display_message(&self, error_type: &str) -> String {
304        match error_type {
305            "std::io::Error" => "IO operation failed: {0}",
306            "serde_json::Error" => "JSON parsing error: {0}",
307            "reqwest::Error" => "HTTP request failed: {0}",
308            "toml::de::Error" => "TOML parsing error: {0}",
309            "sqlx::Error" => "Database error: {0}",
310            "validator::ValidationErrors" => "Validation failed: {0}",
311            _ => "{0}",
312        }
313            .to_string()
314    }
315    fn generate_error_handling_patterns(
316        &self,
317        analysis: &ErrorAnalysis,
318    ) -> Result<String> {
319        let mut code = "// Error handling patterns and best practices\n\n".to_string();
320        code.push_str("/// Common error handling patterns\n");
321        code.push_str("pub mod error_patterns {\n\n");
322        code.push_str("    use super::*;\n\n");
323        for error_type in &analysis.error_types {
324            let pattern_name = self
325                .error_type_to_variant_name(error_type)
326                .to_lowercase();
327            code.push_str("    /// Handle ");
328            code.push_str(error_type);
329            code.push_str(" errors\n");
330            code.push_str("    pub fn ");
331            code.push_str(&pattern_name);
332            code.push_str("_handler<T, F>(operation: F) -> Result<T>\n");
333            code.push_str("    where\n");
334            code.push_str("        F: FnOnce() -> ");
335            code.push_str(error_type);
336            code.push_str(",\n");
337            code.push_str("    {\n");
338            code.push_str("        match operation() {\n");
339            code.push_str("            Ok(result) => Ok(result),\n");
340            code.push_str("            Err(err) => Err(err.into()),\n");
341            code.push_str("        }\n");
342            code.push_str("    }\n\n");
343        }
344        code.push_str("    /// Log and convert errors\n");
345        code.push_str(
346            "    pub fn log_and_convert<E: fmt::Display>(err: E, context: &str) -> AppError {\n",
347        );
348        code.push_str("        let msg = format!(\"{}: {}\", context, err);\n");
349        code.push_str("        log::error!(\"{}\", msg);\n");
350        code.push_str("        AppError::unknown(msg)\n");
351        code.push_str("    }\n\n");
352        code.push_str("    /// Handle async operations with proper error conversion\n");
353        code.push_str(
354            "    pub async fn async_error_handler<F, Fut, T, E>(future: F, context: &str) -> Result<T>\n",
355        );
356        code.push_str("    where\n");
357        code.push_str("        F: FnOnce() -> Fut,\n");
358        code.push_str(
359            "        Fut: std::future::Future<Output = std::result::Result<T, E>>,\n",
360        );
361        code.push_str("        E: fmt::Display + Send + Sync + 'static,\n");
362        code.push_str("    {\n");
363        code.push_str("        future().await.map_err(|err| {\n");
364        code.push_str("            log_and_convert(err, context)\n");
365        code.push_str("        })\n");
366        code.push_str("    }\n\n");
367        code.push_str("}\n\n");
368        Ok(code)
369    }
370}
371struct ErrorUsageVisitor {
372    error_patterns: Vec<ErrorPattern>,
373    error_types: Vec<String>,
374    error_handling_patterns: Vec<String>,
375}
376impl ErrorUsageVisitor {
377    fn new() -> Self {
378        Self {
379            error_patterns: Vec::new(),
380            error_types: Vec::new(),
381            error_handling_patterns: Vec::new(),
382        }
383    }
384}
385impl<'ast> syn::visit::Visit<'ast> for ErrorUsageVisitor {
386    fn visit_item_fn(&mut self, node: &'ast ItemFn) {
387        if let ReturnType::Type(_, ty) = &node.sig.output {
388            self.analyze_return_type(ty);
389        }
390        syn::visit::visit_item_fn(self, node);
391    }
392    fn visit_item_impl(&mut self, node: &'ast ItemImpl) {
393        syn::visit::visit_item_impl(self, node);
394    }
395}
396impl ErrorUsageVisitor {
397    fn analyze_return_type(&mut self, ty: &Type) {
398        match ty {
399            Type::Path(type_path) => {
400                let type_name = type_path
401                    .path
402                    .segments
403                    .iter()
404                    .map(|seg| seg.ident.to_string())
405                    .collect::<Vec<_>>()
406                    .join("::");
407                if type_name.contains("Result") || type_name.contains("Error") {
408                    self.error_types.push(type_name);
409                }
410            }
411            Type::Reference(type_ref) => {
412                self.analyze_return_type(&*type_ref.elem);
413            }
414            _ => {}
415        }
416    }
417}
418impl Tool for ErrorDeriveTool {
419    fn name(&self) -> &'static str {
420        "error-derive"
421    }
422    fn description(&self) -> &'static str {
423        "Generate comprehensive error types with proper Display, Error, From traits"
424    }
425    fn command(&self) -> Command {
426        Command::new(self.name())
427            .about(self.description())
428            .long_about(
429                "Analyze existing error handling patterns in your codebase and generate comprehensive error types with proper Display, Error, From traits, context methods, and error handling patterns. Supports thiserror derive macros and backtrace support.",
430            )
431            .args(
432                &[
433                    Arg::new("input")
434                        .long("input")
435                        .short('i')
436                        .help("Input directory or file to analyze")
437                        .default_value("src/"),
438                    Arg::new("output")
439                        .long("output")
440                        .short('o')
441                        .help("Output file for generated error types")
442                        .default_value("src/errors.rs"),
443                    Arg::new("name")
444                        .long("name")
445                        .short('n')
446                        .help("Name for the generated error enum")
447                        .default_value("AppError"),
448                    Arg::new("thiserror")
449                        .long("thiserror")
450                        .help("Use thiserror derive macros")
451                        .action(clap::ArgAction::SetTrue),
452                    Arg::new("backtrace")
453                        .long("backtrace")
454                        .help("Include backtrace support")
455                        .action(clap::ArgAction::SetTrue),
456                    Arg::new("context")
457                        .long("context")
458                        .help("Generate context-adding methods")
459                        .action(clap::ArgAction::SetTrue),
460                    Arg::new("patterns")
461                        .long("patterns")
462                        .help("Generate error handling patterns")
463                        .action(clap::ArgAction::SetTrue),
464                ],
465            )
466            .args(&common_options())
467    }
468    fn execute(&self, matches: &ArgMatches) -> Result<()> {
469        let input = matches.get_one::<String>("input").unwrap();
470        let output = matches.get_one::<String>("output").unwrap();
471        let name = matches.get_one::<String>("name").unwrap();
472        let thiserror = matches.get_flag("thiserror");
473        let backtrace = matches.get_flag("backtrace");
474        let context = matches.get_flag("context");
475        let patterns = matches.get_flag("patterns");
476        let dry_run = matches.get_flag("dry-run");
477        let verbose = matches.get_flag("verbose");
478        let output_format = parse_output_format(matches);
479        println!(
480            "šŸ”§ {} - {}", "CargoMate ErrorDerive".bold().blue(), self.description()
481            .cyan()
482        );
483        let analysis = if Path::new(input).is_dir() {
484            self.analyze_directory(input)?
485        } else {
486            self.analyze_error_usage(input)?
487        };
488        if verbose {
489            println!("   šŸ“Š Analysis Results:");
490            println!("     • Found {} error types", analysis.error_types.len());
491            println!("     • Found {} error patterns", analysis.patterns.len());
492            println!(
493                "     • Found {} handling patterns", analysis.error_handling_patterns
494                .len()
495            );
496            if !analysis.error_types.is_empty() {
497                println!("     • Error types: {:?}", analysis.error_types);
498            }
499        }
500        let mut error_code = self.generate_comprehensive_error_type(&analysis, name)?;
501        if context {
502            let context_code = self.generate_context_methods(name)?;
503            error_code.push_str("\n");
504            error_code.push_str(&context_code);
505        }
506        if patterns {
507            let pattern_code = self.generate_error_handling_patterns(&analysis)?;
508            error_code.push_str("\n");
509            error_code.push_str(&pattern_code);
510        }
511        if backtrace {
512            let backtrace_code = format!(
513                "
514impl {} {{
515    /// Create error with backtrace
516    pub fn with_backtrace(self) -> Self {{
517        // Add backtrace capture logic here
518        self
519    }}
520}}
521",
522                name
523            );
524            error_code.push_str(&backtrace_code);
525        }
526        match output_format {
527            OutputFormat::Human => {
528                println!(
529                    "  āœ… Generated comprehensive error types for {}", name.bold()
530                );
531                println!("     → {}", output.cyan());
532                if thiserror {
533                    println!("  āœ… Added thiserror derive macros");
534                }
535                if backtrace {
536                    println!("  āœ… Added backtrace support");
537                }
538                if context {
539                    println!("  āœ… Generated context-adding methods");
540                }
541                if patterns {
542                    println!("  āœ… Generated error handling patterns");
543                }
544                if dry_run {
545                    println!("   šŸ“‹ {}", "Generated code preview:".bold());
546                    println!("   {}", "─".repeat(50));
547                    for (i, line) in error_code.lines().take(20).enumerate() {
548                        if i < 19 {
549                            println!("   {}", line);
550                        } else {
551                            println!("   ... (truncated)");
552                            break;
553                        }
554                    }
555                } else {
556                    if let Some(parent) = Path::new(output).parent() {
557                        fs::create_dir_all(parent)
558                            .map_err(|e| ToolError::ExecutionFailed(
559                                format!("Failed to create output directory: {}", e),
560                            ))?;
561                    }
562                    fs::write(output, error_code)
563                        .map_err(|e| ToolError::ExecutionFailed(
564                            format!("Failed to write {}: {}", output, e),
565                        ))?;
566                    println!("  šŸ’¾ File written successfully");
567                }
568            }
569            OutputFormat::Json => {
570                let result = serde_json::json!(
571                    { "error_name" : name, "input" : input, "output" : output,
572                    "error_types_found" : analysis.error_types.len(), "patterns_found" :
573                    analysis.patterns.len(), "thiserror_enabled" : thiserror,
574                    "backtrace_enabled" : backtrace, "context_enabled" : context,
575                    "patterns_enabled" : patterns, "error_types" : analysis.error_types,
576                    "code_preview" : error_code.lines().take(10).collect::< Vec < _ >> ()
577                    .join("\n") }
578                );
579                println!("{}", serde_json::to_string_pretty(& result).unwrap());
580            }
581            OutputFormat::Table => {
582                println!(
583                    "{:<15} {:<10} {:<8} {:<8} {:<8} {:<8}", "Error Type", "Input",
584                    "ThisErr", "Backtrace", "Context", "Patterns"
585                );
586                println!("{}", "─".repeat(70));
587                println!(
588                    "{:<15} {:<10} {:<8} {:<8} {:<8} {:<8}", name, Path::new(input)
589                    .file_name().unwrap_or_default().to_string_lossy(), if thiserror {
590                    "Yes" } else { "No" }, if backtrace { "Yes" } else { "No" }, if
591                    context { "Yes" } else { "No" }, if patterns { "Yes" } else { "No" }
592                );
593            }
594        }
595        println!("\nšŸŽ‰ Error type generation completed!");
596        Ok(())
597    }
598}
599impl Default for ErrorDeriveTool {
600    fn default() -> Self {
601        Self::new()
602    }
603}