1use anyhow::{Context, Result};
2use colored::*;
3use regex::Regex;
4use reqwest;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::PathBuf;
9#[derive(Debug, Clone)]
10pub struct SmartError {
11 pub code: String,
12 pub message: String,
13 pub file: PathBuf,
14 pub line: usize,
15 pub column: usize,
16 pub suggestion: Option<String>,
17 pub fix_command: Option<String>,
18 pub explanation: Option<String>,
19 pub related_docs: Vec<String>,
20}
21#[derive(Debug, Serialize, Deserialize)]
22struct ErrorPattern {
23 code: String,
24 pattern: String,
25 suggestion: String,
26 fix_command: Option<String>,
27 docs_url: Option<String>,
28}
29#[derive(Debug, Serialize, Deserialize)]
30struct ErrorDatabase {
31 patterns: Vec<ErrorPattern>,
32 common_fixes: HashMap<String, Vec<String>>,
33 learning_data: HashMap<String, FixHistory>,
34}
35#[derive(Debug, Serialize, Deserialize)]
36struct FixHistory {
37 successful_fixes: Vec<String>,
38 failed_attempts: Vec<String>,
39 frequency: usize,
40}
41pub struct SmartParser {
42 error_db: ErrorDatabase,
43 db_file: PathBuf,
44 online_search: bool,
45}
46impl SmartParser {
47 pub fn new() -> Result<Self> {
48 let db_file = dirs::home_dir()
49 .context("Could not find home directory")?
50 .join(".shipwreck")
51 .join("error_db.json");
52 let error_db = if db_file.exists() {
53 let content = fs::read_to_string(&db_file)?;
54 serde_json::from_str(&content)?
55 } else {
56 Self::create_default_database()
57 };
58 Ok(Self {
59 error_db,
60 db_file,
61 online_search: true,
62 })
63 }
64 fn create_default_database() -> ErrorDatabase {
65 let patterns = vec![
66 ErrorPattern { code : "E0308".to_string(), pattern : "mismatched types"
67 .to_string(), suggestion :
68 "Check the expected and found types. You may need to convert types using methods like .to_string(), .into(), or as_ref()"
69 .to_string(), fix_command : Some("cargo fix --allow-dirty".to_string()),
70 docs_url : Some("https://doc.rust-lang.org/error-index.html#E0308"
71 .to_string()), }, ErrorPattern { code : "E0382".to_string(), pattern :
72 "borrow of moved value".to_string(), suggestion :
73 "The value has been moved. Consider cloning it, using a reference, or restructuring ownership"
74 .to_string(), fix_command : None, docs_url :
75 Some("https://doc.rust-lang.org/error-index.html#E0382".to_string()), },
76 ErrorPattern { code : "E0499".to_string(), pattern :
77 "cannot borrow .* as mutable more than once".to_string(), suggestion :
78 "You have multiple mutable borrows. Consider using RefCell for interior mutability or restructuring your code"
79 .to_string(), fix_command : None, docs_url :
80 Some("https://doc.rust-lang.org/error-index.html#E0499".to_string()), },
81 ErrorPattern { code : "E0277".to_string(), pattern :
82 "the trait bound .* is not satisfied".to_string(), suggestion :
83 "The required trait is not implemented. Check if you need to import the trait or implement it for your type"
84 .to_string(), fix_command : None, docs_url :
85 Some("https://doc.rust-lang.org/error-index.html#E0277".to_string()), },
86 ErrorPattern { code : "E0433".to_string(), pattern : "failed to resolve"
87 .to_string(), suggestion :
88 "Module or item not found. Check your imports and module declarations"
89 .to_string(), fix_command : Some("cargo check".to_string()), docs_url :
90 Some("https://doc.rust-lang.org/error-index.html#E0433".to_string()), },
91 ];
92 let mut common_fixes = HashMap::new();
93 common_fixes
94 .insert(
95 "lifetime".to_string(),
96 vec![
97 "Add lifetime parameters to your struct/function".to_string(),
98 "Use 'static lifetime if the value needs to live for the entire program"
99 .to_string(), "Consider using Arc<T> or Rc<T> for shared ownership"
100 .to_string(),
101 ],
102 );
103 common_fixes
104 .insert(
105 "async".to_string(),
106 vec![
107 "Make sure you're using .await on async functions".to_string(),
108 "Check if your function needs to be async".to_string(),
109 "Consider using tokio::spawn or async blocks".to_string(),
110 ],
111 );
112 common_fixes
113 .insert(
114 "iterator".to_string(),
115 vec![
116 "Check if you need to call .collect() to consume the iterator"
117 .to_string(),
118 "Make sure you're using the right iterator method (map, filter, fold, etc.)"
119 .to_string(),
120 "Consider using .iter() or .into_iter() based on ownership needs"
121 .to_string(),
122 ],
123 );
124 ErrorDatabase {
125 patterns,
126 common_fixes,
127 learning_data: HashMap::new(),
128 }
129 }
130 pub fn parse_error(&mut self, error_text: &str) -> SmartError {
131 let code = self.extract_error_code(error_text);
132 let (file, line, column) = self.extract_location(error_text);
133 let message = self.extract_message(error_text);
134 let suggestion = self.get_suggestion(&code, &message);
135 let fix_command = self.get_fix_command(&code);
136 let explanation = self.get_explanation(&code, &message);
137 let related_docs = self.get_related_docs(&code);
138 self.learn_from_error(&code, &message);
139 SmartError {
140 code,
141 message,
142 file,
143 line,
144 column,
145 suggestion,
146 fix_command,
147 explanation,
148 related_docs,
149 }
150 }
151 fn extract_error_code(&self, text: &str) -> String {
152 let re = Regex::new(r"error\[([A-Z0-9]+)\]").unwrap();
153 re.captures(text)
154 .and_then(|cap| cap.get(1))
155 .map(|m| m.as_str().to_string())
156 .unwrap_or_else(|| "unknown".to_string())
157 }
158 fn extract_location(&self, text: &str) -> (PathBuf, usize, usize) {
159 let re = Regex::new(r"(\S+\.rs):(\d+):(\d+)").unwrap();
160 if let Some(cap) = re.captures(text) {
161 let file = PathBuf::from(cap.get(1).unwrap().as_str());
162 let line = cap.get(2).unwrap().as_str().parse().unwrap_or(0);
163 let column = cap.get(3).unwrap().as_str().parse().unwrap_or(0);
164 (file, line, column)
165 } else {
166 (PathBuf::from("unknown"), 0, 0)
167 }
168 }
169 fn extract_message(&self, text: &str) -> String {
170 let lines: Vec<&str> = text.lines().collect();
171 if let Some(error_line) = lines.iter().find(|l| l.contains("error")) {
172 let parts: Vec<&str> = error_line.split(':').collect();
173 if parts.len() > 1 {
174 return parts[1..].join(":").trim().to_string();
175 }
176 }
177 text.to_string()
178 }
179 fn get_suggestion(&self, code: &str, message: &str) -> Option<String> {
180 for pattern in &self.error_db.patterns {
181 if pattern.code == code {
182 return Some(pattern.suggestion.clone());
183 }
184 }
185 if message.contains("lifetime") {
186 if let Some(fixes) = self.error_db.common_fixes.get("lifetime") {
187 return Some(fixes.join("\n"));
188 }
189 }
190 if message.contains("async") || message.contains("await") {
191 if let Some(fixes) = self.error_db.common_fixes.get("async") {
192 return Some(fixes.join("\n"));
193 }
194 }
195 if self.online_search {
196 self.search_online_suggestion(code, message).ok()
197 } else {
198 None
199 }
200 }
201 fn get_fix_command(&self, code: &str) -> Option<String> {
202 self.error_db
203 .patterns
204 .iter()
205 .find(|p| p.code == code)
206 .and_then(|p| p.fix_command.clone())
207 }
208 fn get_explanation(&self, code: &str, message: &str) -> Option<String> {
209 let base_explanation = match code {
210 "E0308" => {
211 "Type mismatch occurs when Rust expects one type but finds another."
212 }
213 "E0382" => {
214 "Ownership has been transferred. In Rust, each value has a single owner."
215 }
216 "E0499" => {
217 "Rust's borrowing rules prevent multiple mutable references to prevent data races."
218 }
219 "E0277" => {
220 "A trait bound was not satisfied. The type doesn't implement the required trait."
221 }
222 "E0433" => "Failed to resolve a path to a module, type, or function.",
223 _ => return None,
224 };
225 Some(format!("{}\n\nContext: {}", base_explanation, message))
226 }
227 fn get_related_docs(&self, code: &str) -> Vec<String> {
228 let mut docs = vec![
229 format!("https://doc.rust-lang.org/error-index.html#{}", code),
230 ];
231 if let Some(pattern) = self.error_db.patterns.iter().find(|p| p.code == code) {
232 if let Some(ref url) = pattern.docs_url {
233 docs.push(url.clone());
234 }
235 }
236 docs.push("https://doc.rust-lang.org/book/".to_string());
237 docs
238 }
239 fn search_online_suggestion(&self, code: &str, message: &str) -> Result<String> {
240 Ok(
241 format!(
242 "š” Search for solutions:\n - Stack Overflow: rust {} {}\n - Rust Forum: https://users.rust-lang.org/",
243 code, message.split_whitespace().take(5).collect::< Vec < _ >> ()
244 .join(" ")
245 ),
246 )
247 }
248 fn learn_from_error(&mut self, code: &str, _message: &str) {
249 let entry = self
250 .error_db
251 .learning_data
252 .entry(code.to_string())
253 .or_insert(FixHistory {
254 successful_fixes: Vec::new(),
255 failed_attempts: Vec::new(),
256 frequency: 0,
257 });
258 entry.frequency += 1;
259 let _ = self.save_database();
260 }
261 pub fn record_fix(&mut self, code: &str, fix: &str, successful: bool) -> Result<()> {
262 let entry = self
263 .error_db
264 .learning_data
265 .entry(code.to_string())
266 .or_insert(FixHistory {
267 successful_fixes: Vec::new(),
268 failed_attempts: Vec::new(),
269 frequency: 0,
270 });
271 if successful {
272 entry.successful_fixes.push(fix.to_string());
273 } else {
274 entry.failed_attempts.push(fix.to_string());
275 }
276 self.save_database()?;
277 Ok(())
278 }
279 pub fn display_smart_error(&self, error: &SmartError) {
280 println!(
281 "{}", format!("āāā Error {} āāā", error.code) .red().bold()
282 );
283 println!(
284 "š {}:{}:{}", error.file.display(), error.line.to_string().yellow(), error
285 .column
286 );
287 println!("š {}", error.message.white());
288 if let Some(ref suggestion) = error.suggestion {
289 println!("\nš” {}", "Suggestion:".green().bold());
290 for line in suggestion.lines() {
291 println!(" {}", line);
292 }
293 }
294 if let Some(ref cmd) = error.fix_command {
295 println!("\nš§ {}", "Quick fix:".cyan().bold());
296 println!(" {}", cmd.cyan());
297 }
298 if let Some(ref explanation) = error.explanation {
299 println!("\nš {}", "Explanation:".blue().bold());
300 for line in explanation.lines() {
301 println!(" {}", line.dimmed());
302 }
303 }
304 if !error.related_docs.is_empty() {
305 println!("\nš {}", "Related Documentation:".magenta().bold());
306 for doc in &error.related_docs {
307 println!(" ⢠{}", doc.underline());
308 }
309 }
310 println!("{}", "ā".repeat(50).red());
311 }
312 pub fn suggest_learning_path(&self, errors: &[SmartError]) {
313 let mut error_categories: HashMap<String, usize> = HashMap::new();
314 for error in errors {
315 let category = self.categorize_error(&error.code);
316 *error_categories.entry(category).or_insert(0) += 1;
317 }
318 if error_categories.is_empty() {
319 return;
320 }
321 println!("{}", "š Learning Path Recommendation".green().bold());
322 println!("Based on your errors, consider studying:");
323 let mut categories: Vec<_> = error_categories.into_iter().collect();
324 categories.sort_by(|a, b| b.1.cmp(&a.1));
325 for (category, count) in categories.iter().take(3) {
326 let resources = self.get_learning_resources(category);
327 println!("\n{} ({} errors)", category.cyan(), count);
328 for resource in resources {
329 println!(" ⢠{}", resource);
330 }
331 }
332 }
333 fn categorize_error(&self, code: &str) -> String {
334 match code {
335 c if c.starts_with("E03") => "Ownership & Borrowing".to_string(),
336 c if c.starts_with("E04") => "Pattern Matching".to_string(),
337 c if c.starts_with("E05") => "Traits & Generics".to_string(),
338 c if c.starts_with("E06") => "Modules & Visibility".to_string(),
339 c if c.starts_with("E07") => "Async & Concurrency".to_string(),
340 _ => "General Rust".to_string(),
341 }
342 }
343 fn get_learning_resources(&self, category: &str) -> Vec<String> {
344 match category {
345 "Ownership & Borrowing" => {
346 vec![
347 "The Rust Book - Chapter 4: Understanding Ownership".to_string(),
348 "Rust by Example - Ownership section".to_string(),
349 "Video: 'Rust Ownership Explained' by No Boilerplate".to_string(),
350 ]
351 }
352 "Traits & Generics" => {
353 vec![
354 "The Rust Book - Chapter 10: Generic Types, Traits".to_string(),
355 "Rust by Example - Traits section".to_string(),
356 "Blog: 'Rust Traits: A Deep Dive'".to_string(),
357 ]
358 }
359 _ => {
360 vec![
361 "The Rust Programming Language Book".to_string(), "Rust by Example"
362 .to_string(), "Rustlings exercises".to_string(),
363 ]
364 }
365 }
366 }
367 fn save_database(&self) -> Result<()> {
368 let json = serde_json::to_string_pretty(&self.error_db)?;
369 fs::write(&self.db_file, json)?;
370 Ok(())
371 }
372}