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}