1use anyhow::Result;
4use std::fmt;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone)]
9pub enum DictatorError {
10 ConfigError {
12 file: PathBuf,
13 line: Option<usize>,
14 message: String,
15 suggestion: String,
16 },
17
18 WasmLoadError {
20 path: PathBuf,
21 abi_version: String,
22 expected: String,
23 suggestion: String,
24 },
25
26 FileProcessingError {
28 path: PathBuf,
29 operation: String,
30 message: String,
31 suggestion: String,
32 },
33
34 RuleConfigurationError {
36 decree: String,
37 rule: String,
38 message: String,
39 suggestion: String,
40 },
41
42 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
125pub trait DictatorContext<T> {
127 fn config_context(self, file: PathBuf, line: Option<usize>, suggestion: &str) -> Result<T>;
129
130 fn wasm_context(
132 self,
133 path: PathBuf,
134 abi_version: &str,
135 expected: &str,
136 suggestion: &str,
137 ) -> Result<T>;
138
139 fn file_context(self, path: PathBuf, operation: &str, suggestion: &str) -> Result<T>;
141
142 fn rule_context(self, decree: &str, rule: &str, suggestion: &str) -> Result<T>;
144
145 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 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
220pub mod suggestions {
222
223 #[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 #[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 #[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 #[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}