Skip to main content

dictator_core/
error.rs

1//! Enhanced error context and diagnostics for Dictator
2
3use anyhow::Result;
4use std::fmt;
5use std::path::PathBuf;
6
7/// Enhanced error types with context and suggestions
8#[derive(Debug, Clone)]
9pub enum DictatorError {
10    /// Configuration-related errors
11    ConfigError {
12        file: PathBuf,
13        line: Option<usize>,
14        message: String,
15        suggestion: String,
16    },
17
18    /// WASM decree loading errors
19    WasmLoadError {
20        path: PathBuf,
21        abi_version: String,
22        expected: String,
23        suggestion: String,
24    },
25
26    /// File processing errors
27    FileProcessingError {
28        path: PathBuf,
29        operation: String,
30        message: String,
31        suggestion: String,
32    },
33
34    /// Rule configuration errors
35    RuleConfigurationError {
36        decree: String,
37        rule: String,
38        message: String,
39        suggestion: String,
40    },
41
42    /// Performance-related warnings
43    PerformanceWarning {
44        context: String,
45        message: String,
46        suggestion: String,
47    },
48}
49
50impl fmt::Display for DictatorError {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::ConfigError {
54                file,
55                line,
56                message,
57                suggestion,
58            } => {
59                write!(f, "Configuration error")?;
60                if let Some(line_num) = line {
61                    write!(f, " at {}:{}", file.display(), line_num)?;
62                } else {
63                    write!(f, " in {}", file.display())?;
64                }
65                write!(f, ": {message}")?;
66                write!(f, "\nšŸ’” Suggestion: {suggestion}")?;
67            }
68            Self::WasmLoadError {
69                path,
70                abi_version,
71                expected,
72                suggestion,
73            } => {
74                write!(
75                    f,
76                    "WASM decree load error for {}: ABI version {} doesn't match expected {}",
77                    path.display(),
78                    abi_version,
79                    expected
80                )?;
81                write!(f, "\nšŸ’” Suggestion: {suggestion}")?;
82            }
83            Self::FileProcessingError {
84                path,
85                operation,
86                message,
87                suggestion,
88            } => {
89                write!(
90                    f,
91                    "File processing error during '{}' for {}: {}",
92                    operation,
93                    path.display(),
94                    message
95                )?;
96                write!(f, "\nšŸ’” Suggestion: {suggestion}")?;
97            }
98            Self::RuleConfigurationError {
99                decree,
100                rule,
101                message,
102                suggestion,
103            } => {
104                write!(
105                    f,
106                    "Rule configuration error for {decree}::{rule}: {message}"
107                )?;
108                write!(f, "\nšŸ’” Suggestion: {suggestion}")?;
109            }
110            Self::PerformanceWarning {
111                context,
112                message,
113                suggestion,
114            } => {
115                write!(f, "Performance warning ({context}): {message}")?;
116                write!(f, "\nšŸ’” Suggestion: {suggestion}")?;
117            }
118        }
119        Ok(())
120    }
121}
122
123impl std::error::Error for DictatorError {}
124
125/// Extension trait for adding Dictator-specific context to Results
126pub trait DictatorContext<T> {
127    /// Add configuration error context
128    fn config_context(self, file: PathBuf, line: Option<usize>, suggestion: &str) -> Result<T>;
129
130    /// Add WASM loading error context
131    fn wasm_context(
132        self,
133        path: PathBuf,
134        abi_version: &str,
135        expected: &str,
136        suggestion: &str,
137    ) -> Result<T>;
138
139    /// Add file processing error context
140    fn file_context(self, path: PathBuf, operation: &str, suggestion: &str) -> Result<T>;
141
142    /// Add rule configuration error context
143    fn rule_context(self, decree: &str, rule: &str, suggestion: &str) -> Result<T>;
144
145    /// Add performance warning context
146    fn performance_context(self, context: &str, suggestion: &str) -> Result<T>;
147}
148
149impl<T, E> DictatorContext<T> for Result<T, E>
150where
151    E: Into<anyhow::Error>,
152{
153    fn config_context(self, file: PathBuf, line: Option<usize>, suggestion: &str) -> Result<T> {
154        self.map_err(|e| {
155            let error = e.into();
156            anyhow::Error::new(DictatorError::ConfigError {
157                file,
158                line,
159                message: error.to_string(),
160                suggestion: suggestion.to_string(),
161            })
162        })
163    }
164
165    fn wasm_context(
166        self,
167        path: PathBuf,
168        abi_version: &str,
169        expected: &str,
170        suggestion: &str,
171    ) -> Result<T> {
172        self.map_err(|e| {
173            // Original error discarded; WASM errors use explicit params
174            let _: anyhow::Error = e.into();
175            anyhow::Error::new(DictatorError::WasmLoadError {
176                path,
177                abi_version: abi_version.to_string(),
178                expected: expected.to_string(),
179                suggestion: suggestion.to_string(),
180            })
181        })
182    }
183
184    fn file_context(self, path: PathBuf, operation: &str, suggestion: &str) -> Result<T> {
185        self.map_err(|e| {
186            let error: anyhow::Error = e.into();
187            anyhow::Error::new(DictatorError::FileProcessingError {
188                path,
189                operation: operation.to_string(),
190                message: error.to_string(),
191                suggestion: suggestion.to_string(),
192            })
193        })
194    }
195
196    fn rule_context(self, decree: &str, rule: &str, suggestion: &str) -> Result<T> {
197        self.map_err(|e| {
198            let error = e.into();
199            anyhow::Error::new(DictatorError::RuleConfigurationError {
200                decree: decree.to_string(),
201                rule: rule.to_string(),
202                message: error.to_string(),
203                suggestion: suggestion.to_string(),
204            })
205        })
206    }
207
208    fn performance_context(self, context: &str, suggestion: &str) -> Result<T> {
209        self.map_err(|e| {
210            let error = e.into();
211            anyhow::Error::new(DictatorError::PerformanceWarning {
212                context: context.to_string(),
213                message: error.to_string(),
214                suggestion: suggestion.to_string(),
215            })
216        })
217    }
218}
219
220/// Helper functions for common error scenarios
221pub mod suggestions {
222
223    /// Suggestions for configuration errors
224    #[must_use]
225    pub fn config_suggestions(error_type: &str) -> &'static str {
226        match error_type {
227            "invalid_toml" => {
228                "Check TOML syntax using a validator. \
229                 Ensure all brackets and quotes are properly closed."
230            }
231            "invalid_value" => {
232                "Check the documentation for valid configuration values. \
233                 Use 'dictator config validate' to verify your configuration."
234            }
235            "missing_field" => {
236                "Add the required field to your configuration. \
237                 Run 'dictator occupy' to generate a default configuration."
238            }
239            "file_not_found" => {
240                "Ensure the configuration file exists and is readable. \
241                 The default location is .dictate.toml in your project root."
242            }
243            _ => {
244                "Check the error message and refer to the documentation \
245                 for proper configuration syntax."
246            }
247        }
248    }
249
250    /// Suggestions for WASM loading errors
251    #[must_use]
252    pub fn wasm_suggestions(error_type: &str) -> &'static str {
253        match error_type {
254            "abi_mismatch" => {
255                "Rebuild your WASM decree with the same dictator-decree-abi version \
256                 as the host. Check 'dictator --version' for ABI compatibility."
257            }
258            "invalid_wasm" => {
259                "Ensure your decree is compiled as a WASM component with the correct \
260                 target (wasm32-wasip1). Check the build documentation."
261            }
262            "file_not_found" => {
263                "Verify the WASM file path is correct and the file exists. \
264                 Use absolute paths if relative paths are ambiguous."
265            }
266            "permission_denied" => {
267                "Check file permissions and ensure the WASM file is readable \
268                 by the current user."
269            }
270            _ => {
271                "Check that your WASM decree is properly built and compatible \
272                 with the current Dictator version."
273            }
274        }
275    }
276
277    /// Suggestions for file processing errors
278    #[must_use]
279    pub fn file_suggestions(error_type: &str) -> &'static str {
280        match error_type {
281            "read_error" => {
282                "Check file permissions and ensure the file exists. \
283                 Files may be locked by other processes."
284            }
285            "encoding_error" => {
286                "Ensure files are UTF-8 encoded. \
287                 Convert files from other encodings using iconv or similar tools."
288            }
289            "large_file" => {
290                "Consider splitting large files into smaller modules \
291                 or increasing max_lines in your configuration."
292            }
293            _ => {
294                "Check file permissions, encoding, and ensure files \
295                 are not corrupted or locked."
296            }
297        }
298    }
299
300    /// Suggestions for performance optimization
301    #[must_use]
302    pub fn performance_suggestions(issue: &str) -> &'static str {
303        match issue {
304            "many_files" => {
305                "Consider using file patterns to limit the scope or run Dictator \
306                 on specific directories instead of the entire project."
307            }
308            "large_files" => {
309                "Split large files into smaller modules or increase memory limits. \
310                 Consider using streaming mode for very large files."
311            }
312            "slow_wasm" => {
313                "Check if WASM decrees are causing performance issues. \
314                 Consider using native decrees or optimizing your WASM components."
315            }
316            "cache_miss" => {
317                "Ensure WASM caching is enabled and working. \
318                 Check cache statistics with appropriate verbosity flags."
319            }
320            _ => {
321                "Monitor system resources and consider optimizing \
322                 your project structure or Dictator configuration."
323            }
324        }
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use std::path::PathBuf;
332
333    #[test]
334    fn test_config_error_display() {
335        let error = DictatorError::ConfigError {
336            file: PathBuf::from(".dictate.toml"),
337            line: Some(42),
338            message: "Invalid TOML syntax".to_string(),
339            suggestion: "Check your brackets".to_string(),
340        };
341
342        let output = format!("{error}");
343        assert!(output.contains("Configuration error"));
344        assert!(output.contains(".dictate.toml:42"));
345        assert!(output.contains("Invalid TOML syntax"));
346        assert!(output.contains("šŸ’” Suggestion: Check your brackets"));
347    }
348
349    #[test]
350    fn test_context_extension() {
351        use std::fs;
352
353        let result: Result<String, std::io::Error> = fs::read_to_string("/nonexistent/file.toml");
354        let enhanced_result = result.config_context(
355            PathBuf::from(".dictate.toml"),
356            Some(1),
357            "Ensure the file exists and is readable",
358        );
359
360        assert!(enhanced_result.is_err());
361        let error_msg = enhanced_result.unwrap_err().to_string();
362        assert!(error_msg.contains("Configuration error"));
363        assert!(error_msg.contains("šŸ’” Suggestion:"));
364    }
365}