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
79impl PostCSSError {
80    /// Create a configuration error
81    pub fn config(message: &str) -> Self {
82        PostCSSError::ConfigError {
83            message: message.to_string(),
84        }
85    }
86}
87
88/// Error context for better debugging
89#[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/// Enhanced error with context
127#[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/// Error recovery strategies
173#[derive(Debug, Clone)]
174pub enum RecoveryStrategy {
175    /// Skip the problematic operation and continue
176    Skip,
177    /// Use a fallback value
178    Fallback(String),
179    /// Retry with different parameters
180    Retry,
181    /// Abort the entire operation
182    Abort,
183}
184
185/// Error handler trait for custom error handling
186pub 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/// Default error handler implementation
193#[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
210/// Error reporting utilities
211pub 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}