tailwind_rs_postcss/
error.rs

1//! Error handling for PostCSS integration
2//!
3//! This module provides comprehensive error types and handling for all
4//! PostCSS-related operations.
5
6use thiserror::Error;
7
8/// Main error type for PostCSS operations
9#[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
58/// Result type alias for PostCSS operations
59pub 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/// Error context for better debugging
80#[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/// Enhanced error with context
118#[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/// Error recovery strategies
164#[derive(Debug, Clone)]
165pub enum RecoveryStrategy {
166    /// Skip the problematic operation and continue
167    Skip,
168    /// Use a fallback value
169    Fallback(String),
170    /// Retry with different parameters
171    Retry,
172    /// Abort the entire operation
173    Abort,
174}
175
176/// Error handler trait for custom error handling
177pub 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/// Default error handler implementation
184#[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
201/// Error reporting utilities
202pub 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}