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
79#[derive(Debug, Clone)]
81pub struct ErrorContext {
82 pub operation: String,
83 pub file: Option<String>,
84 pub line: Option<usize>,
85 pub column: Option<usize>,
86 pub additional_info: std::collections::HashMap<String, String>,
87}
88
89impl ErrorContext {
90 pub fn new(operation: String) -> Self {
91 Self {
92 operation,
93 file: None,
94 line: None,
95 column: None,
96 additional_info: std::collections::HashMap::new(),
97 }
98 }
99
100 pub fn with_file(mut self, file: String) -> Self {
101 self.file = Some(file);
102 self
103 }
104
105 pub fn with_position(mut self, line: usize, column: usize) -> Self {
106 self.line = Some(line);
107 self.column = Some(column);
108 self
109 }
110
111 pub fn with_info(mut self, key: String, value: String) -> Self {
112 self.additional_info.insert(key, value);
113 self
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct ContextualError {
120 pub error: PostCSSError,
121 pub context: ErrorContext,
122}
123
124impl ContextualError {
125 pub fn new(error: PostCSSError, context: ErrorContext) -> Self {
126 Self { error, context }
127 }
128
129 pub fn with_context(error: PostCSSError, operation: &str) -> Self {
130 Self {
131 error,
132 context: ErrorContext::new(operation.to_string()),
133 }
134 }
135}
136
137impl std::fmt::Display for ContextualError {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 write!(f, "{} in {}", self.error, self.context.operation)?;
140
141 if let Some(file) = &self.context.file {
142 write!(f, " (file: {})", file)?;
143 }
144
145 if let (Some(line), Some(column)) = (self.context.line, self.context.column) {
146 write!(f, " at line {}, column {}", line, column)?;
147 }
148
149 if !self.context.additional_info.is_empty() {
150 write!(f, " [Additional info: ")?;
151 for (key, value) in &self.context.additional_info {
152 write!(f, "{}={}, ", key, value)?;
153 }
154 write!(f, "]")?;
155 }
156
157 Ok(())
158 }
159}
160
161impl std::error::Error for ContextualError {}
162
163#[derive(Debug, Clone)]
165pub enum RecoveryStrategy {
166 Skip,
168 Fallback(String),
170 Retry,
172 Abort,
174}
175
176pub trait ErrorHandler {
178 fn handle_parse_error(&self, error: &PostCSSError) -> RecoveryStrategy;
179 fn handle_plugin_error(&self, error: &PostCSSError) -> RecoveryStrategy;
180 fn handle_transform_error(&self, error: &PostCSSError) -> RecoveryStrategy;
181}
182
183#[derive(Debug, Clone)]
185pub struct DefaultErrorHandler;
186
187impl ErrorHandler for DefaultErrorHandler {
188 fn handle_parse_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
189 RecoveryStrategy::Abort
190 }
191
192 fn handle_plugin_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
193 RecoveryStrategy::Skip
194 }
195
196 fn handle_transform_error(&self, _error: &PostCSSError) -> RecoveryStrategy {
197 RecoveryStrategy::Abort
198 }
199}
200
201pub struct ErrorReporter {
203 errors: Vec<ContextualError>,
204 warnings: Vec<ContextualError>,
205}
206
207impl ErrorReporter {
208 pub fn new() -> Self {
209 Self {
210 errors: Vec::new(),
211 warnings: Vec::new(),
212 }
213 }
214
215 pub fn add_error(&mut self, error: PostCSSError, context: ErrorContext) {
216 self.errors.push(ContextualError::new(error, context));
217 }
218
219 pub fn add_warning(&mut self, error: PostCSSError, context: ErrorContext) {
220 self.warnings.push(ContextualError::new(error, context));
221 }
222
223 pub fn has_errors(&self) -> bool {
224 !self.errors.is_empty()
225 }
226
227 pub fn has_warnings(&self) -> bool {
228 !self.warnings.is_empty()
229 }
230
231 pub fn get_errors(&self) -> &[ContextualError] {
232 &self.errors
233 }
234
235 pub fn get_warnings(&self) -> &[ContextualError] {
236 &self.warnings
237 }
238
239 pub fn clear(&mut self) {
240 self.errors.clear();
241 self.warnings.clear();
242 }
243
244 pub fn to_report(&self) -> String {
245 let mut report = String::new();
246
247 if !self.errors.is_empty() {
248 report.push_str("Errors:\n");
249 for (i, error) in self.errors.iter().enumerate() {
250 report.push_str(&format!(" {}. {}\n", i + 1, error));
251 }
252 }
253
254 if !self.warnings.is_empty() {
255 report.push_str("Warnings:\n");
256 for (i, warning) in self.warnings.iter().enumerate() {
257 report.push_str(&format!(" {}. {}\n", i + 1, warning));
258 }
259 }
260
261 report
262 }
263}
264
265impl Default for ErrorReporter {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_error_creation() {
277 let error = PostCSSError::ParseError {
278 message: "Invalid CSS syntax".to_string(),
279 line: 10,
280 column: 5,
281 };
282
283 assert!(error.to_string().contains("Parse error"));
284 assert!(error.to_string().contains("line 10"));
285 assert!(error.to_string().contains("column 5"));
286 }
287
288 #[test]
289 fn test_error_context() {
290 let context = ErrorContext::new("CSS parsing".to_string())
291 .with_file("styles.css".to_string())
292 .with_position(10, 5)
293 .with_info("selector".to_string(), ".test".to_string());
294
295 assert_eq!(context.operation, "CSS parsing");
296 assert_eq!(context.file, Some("styles.css".to_string()));
297 assert_eq!(context.line, Some(10));
298 assert_eq!(context.column, Some(5));
299 assert_eq!(context.additional_info.get("selector"), Some(&".test".to_string()));
300 }
301
302 #[test]
303 fn test_error_reporter() {
304 let mut reporter = ErrorReporter::new();
305
306 let error = PostCSSError::ParseError {
307 message: "Invalid syntax".to_string(),
308 line: 1,
309 column: 1,
310 };
311 let context = ErrorContext::new("parsing".to_string());
312
313 reporter.add_error(error, context);
314
315 assert!(reporter.has_errors());
316 assert!(!reporter.has_warnings());
317 assert_eq!(reporter.get_errors().len(), 1);
318 }
319
320 #[test]
321 fn test_recovery_strategies() {
322 let handler = DefaultErrorHandler;
323
324 let parse_error = PostCSSError::ParseError {
325 message: "Invalid CSS".to_string(),
326 line: 1,
327 column: 1,
328 };
329
330 let strategy = handler.handle_parse_error(&parse_error);
331 assert!(matches!(strategy, RecoveryStrategy::Abort));
332 }
333}