1use thiserror::Error;
7
8#[derive(Error, Debug, Clone)]
10pub enum PostCSSError {
11 #[error("Parse error: {message} at line {line}, column {column}")]
12 ParseError {
13 message: String,
14 line: usize,
15 column: usize,
16 },
17
18 #[error("Transform error: {message}")]
19 TransformError { message: String },
20
21 #[error("Plugin error: {message}")]
22 PluginError { message: String },
23
24 #[error("JavaScript bridge error: {message}")]
25 JavaScriptBridgeError { message: String },
26
27 #[error("Source map error: {message}")]
28 SourceMapError { message: String },
29
30 #[error("Configuration error: {message}")]
31 ConfigError { message: String },
32
33 #[error("Invalid AST: {0}")]
34 InvalidAST(String),
35
36 #[error("JavaScript bridge not available")]
37 JavaScriptBridgeNotAvailable,
38
39 #[error("Plugin not found: {name}")]
40 PluginNotFound { name: String },
41
42 #[error("Plugin execution failed: {name} - {message}")]
43 PluginExecutionFailed { name: String, message: String },
44
45 #[error("Memory allocation failed: {message}")]
46 MemoryError { message: String },
47
48 #[error("File I/O error: {0}")]
49 IoError(String),
50
51 #[error("Serialization error: {0}")]
52 SerializationError(String),
53
54 #[error("Generic error: {0}")]
55 Generic(String),
56}
57
58pub type Result<T> = std::result::Result<T, PostCSSError>;
60
61impl From<std::io::Error> for PostCSSError {
62 fn from(err: std::io::Error) -> Self {
63 PostCSSError::IoError(err.to_string())
64 }
65}
66
67impl From<serde_json::Error> for PostCSSError {
68 fn from(err: serde_json::Error) -> Self {
69 PostCSSError::SerializationError(err.to_string())
70 }
71}
72
73impl From<anyhow::Error> for PostCSSError {
74 fn from(err: anyhow::Error) -> Self {
75 PostCSSError::Generic(err.to_string())
76 }
77}
78
79impl PostCSSError {
80 pub fn config(message: &str) -> Self {
82 PostCSSError::ConfigError {
83 message: message.to_string(),
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct ErrorContext {
91 pub operation: String,
92 pub file: Option<String>,
93 pub line: Option<usize>,
94 pub column: Option<usize>,
95 pub additional_info: std::collections::HashMap<String, String>,
96}
97
98impl ErrorContext {
99 pub fn new(operation: String) -> Self {
100 Self {
101 operation,
102 file: None,
103 line: None,
104 column: None,
105 additional_info: std::collections::HashMap::new(),
106 }
107 }
108
109 pub fn with_file(mut self, file: String) -> Self {
110 self.file = Some(file);
111 self
112 }
113
114 pub fn with_position(mut self, line: usize, column: usize) -> Self {
115 self.line = Some(line);
116 self.column = Some(column);
117 self
118 }
119
120 pub fn with_info(mut self, key: String, value: String) -> Self {
121 self.additional_info.insert(key, value);
122 self
123 }
124}
125
126#[derive(Debug, Clone)]
128pub struct ContextualError {
129 pub error: PostCSSError,
130 pub context: ErrorContext,
131}
132
133impl ContextualError {
134 pub fn new(error: PostCSSError, context: ErrorContext) -> Self {
135 Self { error, context }
136 }
137
138 pub fn with_context(error: PostCSSError, operation: &str) -> Self {
139 Self {
140 error,
141 context: ErrorContext::new(operation.to_string()),
142 }
143 }
144}
145
146impl std::fmt::Display for ContextualError {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "{} in {}", self.error, self.context.operation)?;
149
150 if let Some(file) = &self.context.file {
151 write!(f, " (file: {})", file)?;
152 }
153
154 if let (Some(line), Some(column)) = (self.context.line, self.context.column) {
155 write!(f, " at line {}, column {}", line, column)?;
156 }
157
158 if !self.context.additional_info.is_empty() {
159 write!(f, " [Additional info: ")?;
160 for (key, value) in &self.context.additional_info {
161 write!(f, "{}={}, ", key, value)?;
162 }
163 write!(f, "]")?;
164 }
165
166 Ok(())
167 }
168}
169
170impl std::error::Error for ContextualError {}
171
172#[derive(Debug, Clone)]
174pub enum RecoveryStrategy {
175 Skip,
177 Fallback(String),
179 Retry,
181 Abort,
183}
184
185pub trait ErrorHandler {
187 fn handle_parse_error(&self, error: &PostCSSError) -> RecoveryStrategy;
188 fn handle_plugin_error(&self, error: &PostCSSError) -> RecoveryStrategy;
189 fn handle_transform_error(&self, error: &PostCSSError) -> RecoveryStrategy;
190}
191
192#[derive(Debug, Clone)]
194pub struct DefaultErrorHandler;
195
196impl ErrorHandler for DefaultErrorHandler {
197 fn handle_parse_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
198 RecoveryStrategy::Abort
199 }
200
201 fn handle_plugin_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
202 RecoveryStrategy::Skip
203 }
204
205 fn handle_transform_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
206 RecoveryStrategy::Abort
207 }
208}
209
210pub struct ErrorReporter {
212 errors: Vec<ContextualError>,
213 warnings: Vec<ContextualError>,
214}
215
216impl ErrorReporter {
217 pub fn new() -> Self {
218 Self {
219 errors: Vec::new(),
220 warnings: Vec::new(),
221 }
222 }
223
224 pub fn add_error(&mut self, error: PostCSSError, context: ErrorContext) {
225 self.errors.push(ContextualError::new(error, context));
226 }
227
228 pub fn add_warning(&mut self, error: PostCSSError, context: ErrorContext) {
229 self.warnings.push(ContextualError::new(error, context));
230 }
231
232 pub fn has_errors(&self) -> bool {
233 !self.errors.is_empty()
234 }
235
236 pub fn has_warnings(&self) -> bool {
237 !self.warnings.is_empty()
238 }
239
240 pub fn get_errors(&self) -> &[ContextualError] {
241 &self.errors
242 }
243
244 pub fn get_warnings(&self) -> &[ContextualError] {
245 &self.warnings
246 }
247
248 pub fn clear(&mut self) {
249 self.errors.clear();
250 self.warnings.clear();
251 }
252
253 pub fn to_report(&self) -> String {
254 let mut report = String::new();
255
256 if !self.errors.is_empty() {
257 report.push_str("Errors:\n");
258 for (i, error) in self.errors.iter().enumerate() {
259 report.push_str(&format!(" {}. {}\n", i + 1, error));
260 }
261 }
262
263 if !self.warnings.is_empty() {
264 report.push_str("Warnings:\n");
265 for (i, warning) in self.warnings.iter().enumerate() {
266 report.push_str(&format!(" {}. {}\n", i + 1, warning));
267 }
268 }
269
270 report
271 }
272}
273
274impl Default for ErrorReporter {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_error_creation() {
286 let error = PostCSSError::ParseError {
287 message: "Invalid CSS syntax".to_string(),
288 line: 10,
289 column: 5,
290 };
291
292 assert!(error.to_string().contains("Parse error"));
293 assert!(error.to_string().contains("line 10"));
294 assert!(error.to_string().contains("column 5"));
295 }
296
297 #[test]
298 fn test_error_context() {
299 let context = ErrorContext::new("CSS parsing".to_string())
300 .with_file("styles.css".to_string())
301 .with_position(10, 5)
302 .with_info("selector".to_string(), ".test".to_string());
303
304 assert_eq!(context.operation, "CSS parsing");
305 assert_eq!(context.file, Some("styles.css".to_string()));
306 assert_eq!(context.line, Some(10));
307 assert_eq!(context.column, Some(5));
308 assert_eq!(
309 context.additional_info.get("selector"),
310 Some(&".test".to_string())
311 );
312 }
313
314 #[test]
315 fn test_error_reporter() {
316 let mut reporter = ErrorReporter::new();
317
318 let error = PostCSSError::ParseError {
319 message: "Invalid syntax".to_string(),
320 line: 1,
321 column: 1,
322 };
323 let context = ErrorContext::new("parsing".to_string());
324
325 reporter.add_error(error, context);
326
327 assert!(reporter.has_errors());
328 assert!(!reporter.has_warnings());
329 assert_eq!(reporter.get_errors().len(), 1);
330 }
331
332 #[test]
333 fn test_recovery_strategies() {
334 let handler = DefaultErrorHandler;
335
336 let parse_error = PostCSSError::ParseError {
337 message: "Invalid CSS".to_string(),
338 line: 1,
339 column: 1,
340 };
341
342 let strategy = handler.handle_parse_error(&parse_error);
343 assert!(matches!(strategy, RecoveryStrategy::Abort));
344 }
345}