dsq_core/
error.rs

1use std::borrow::Cow;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6use dsq_formats;
7#[cfg(feature = "io")]
8use dsq_io;
9
10/// Result type alias for dsq operations
11pub type Result<T> = std::result::Result<T, Error>;
12
13/// Main error type for dsq operations
14#[derive(Debug)]
15pub enum Error {
16    /// I/O errors (file operations, etc.)
17    Io(io::Error),
18
19    /// Polars errors (`DataFrame` operations)
20    Polars(polars::error::PolarsError),
21
22    /// JSON parsing errors
23    Json(serde_json::Error),
24
25    /// Format detection or parsing errors
26    Format(FormatError),
27
28    /// Filter compilation or execution errors
29    Filter(FilterError),
30
31    /// Type conversion errors
32    Type(TypeError),
33
34    /// General operation errors
35    Operation(Cow<'static, str>),
36
37    /// Configuration errors
38    Config(String),
39
40    /// Multiple errors collected during processing
41    Multiple(Vec<Error>),
42}
43
44/// Errors related to file format handling
45#[derive(Debug, Clone)]
46pub enum FormatError {
47    /// Unknown or unsupported file format
48    Unknown(String),
49
50    /// Failed to detect format from file extension
51    DetectionFailed(String),
52
53    /// Format is supported but specific feature is not
54    UnsupportedFeature(String),
55
56    /// Schema mismatch between expected and actual
57    SchemaMismatch {
58        /// Expected schema
59        expected: String,
60        /// Actual schema
61        actual: String,
62    },
63
64    /// Invalid format-specific options
65    InvalidOption(String),
66}
67
68/// Errors related to filter compilation and execution
69#[derive(Debug, Clone)]
70pub enum FilterError {
71    /// Parse error from jaq parser
72    Parse(String),
73
74    /// Compilation error when converting jaq AST to dsq operations
75    Compile(String),
76
77    /// Runtime error during filter execution
78    Runtime(String),
79
80    /// Undefined variable or function
81    Undefined(String),
82
83    /// Type mismatch in filter operation
84    TypeMismatch {
85        /// Expected type
86        expected: String,
87        /// Actual type
88        actual: String,
89    },
90
91    /// Invalid argument count for function
92    ArgumentCount {
93        /// Expected number of arguments
94        expected: usize,
95        /// Actual number of arguments
96        actual: usize,
97    },
98}
99
100/// Type conversion and compatibility errors
101#[derive(Debug, Clone)]
102pub enum TypeError {
103    /// Cannot convert between types
104    InvalidConversion {
105        /// Source type
106        from: String,
107        /// Target type
108        to: String,
109    },
110
111    /// Operation not supported for type
112    UnsupportedOperation {
113        /// Operation name
114        operation: String,
115        /// Type name
116        typ: String,
117    },
118
119    /// Field not found in object or `DataFrame`
120    FieldNotFound {
121        /// Field name
122        field: String,
123        /// Type name
124        typ: String,
125    },
126
127    /// Value out of range for target type
128    OutOfRange(String),
129
130    /// Null value where not expected
131    UnexpectedNull(String),
132}
133
134impl fmt::Display for Error {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Error::Io(e) => write!(f, "I/O error: {e}"),
138            Error::Polars(e) => write!(f, "DataFrame error: {e}"),
139            Error::Json(e) => write!(f, "JSON error: {e}"),
140            Error::Format(e) => write!(f, "Format error: {e}"),
141            Error::Filter(e) => write!(f, "Filter error: {e}"),
142            Error::Type(e) => write!(f, "Type error: {e}"),
143            Error::Operation(msg) => write!(f, "Operation error: {msg}"),
144            Error::Config(msg) => write!(f, "Configuration error: {msg}"),
145            Error::Multiple(errors) => {
146                write!(f, "Multiple errors occurred:")?;
147                for (i, e) in errors.iter().enumerate() {
148                    write!(f, "\n  {}. {}", i + 1, e)?;
149                }
150                Ok(())
151            }
152        }
153    }
154}
155
156impl fmt::Display for FormatError {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            FormatError::Unknown(format) => {
160                write!(f, "Unknown format: {format}")
161            }
162            FormatError::DetectionFailed(path) => {
163                write!(f, "Failed to detect format for: {path}")
164            }
165            FormatError::UnsupportedFeature(feature) => {
166                write!(f, "Unsupported feature: {feature}")
167            }
168            FormatError::SchemaMismatch { expected, actual } => {
169                write!(f, "Schema mismatch: expected {expected}, got {actual}")
170            }
171            FormatError::InvalidOption(option) => {
172                write!(f, "Invalid format option: {option}")
173            }
174        }
175    }
176}
177
178impl fmt::Display for FilterError {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        match self {
181            FilterError::Parse(msg) => write!(f, "Parse error: {msg}"),
182            FilterError::Compile(msg) => write!(f, "Compilation error: {msg}"),
183            FilterError::Runtime(msg) => write!(f, "Runtime error: {msg}"),
184            FilterError::Undefined(name) => write!(f, "Undefined: {name}"),
185            FilterError::TypeMismatch { expected, actual } => {
186                write!(f, "Type mismatch: expected {expected}, got {actual}")
187            }
188            FilterError::ArgumentCount { expected, actual } => {
189                write!(f, "Wrong argument count: expected {expected}, got {actual}")
190            }
191        }
192    }
193}
194
195impl fmt::Display for TypeError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        match self {
198            TypeError::InvalidConversion { from, to } => {
199                write!(f, "Cannot convert from {from} to {to}")
200            }
201            TypeError::UnsupportedOperation { operation, typ } => {
202                write!(f, "Operation '{operation}' not supported for type {typ}")
203            }
204            TypeError::FieldNotFound { field, typ } => {
205                write!(f, "Field '{field}' not found in {typ}")
206            }
207            TypeError::OutOfRange(msg) => write!(f, "Value out of range: {msg}"),
208            TypeError::UnexpectedNull(context) => {
209                write!(f, "Unexpected null value in: {context}")
210            }
211        }
212    }
213}
214
215impl StdError for Error {
216    fn source(&self) -> Option<&(dyn StdError + 'static)> {
217        match self {
218            Error::Io(e) => Some(e),
219            Error::Polars(e) => Some(e),
220            Error::Json(e) => Some(e),
221            _ => None,
222        }
223    }
224}
225
226impl StdError for FormatError {}
227impl StdError for FilterError {}
228impl StdError for TypeError {}
229
230// Conversion implementations
231impl From<io::Error> for Error {
232    fn from(e: io::Error) -> Self {
233        Error::Io(e)
234    }
235}
236
237impl From<polars::error::PolarsError> for Error {
238    fn from(e: polars::error::PolarsError) -> Self {
239        Error::Polars(e)
240    }
241}
242
243impl From<serde_json::Error> for Error {
244    fn from(e: serde_json::Error) -> Self {
245        Error::Json(e)
246    }
247}
248
249impl From<FormatError> for Error {
250    fn from(e: FormatError) -> Self {
251        Error::Format(e)
252    }
253}
254
255impl From<FilterError> for Error {
256    fn from(e: FilterError) -> Self {
257        Error::Filter(e)
258    }
259}
260
261impl From<TypeError> for Error {
262    fn from(e: TypeError) -> Self {
263        Error::Type(e)
264    }
265}
266
267impl From<anyhow::Error> for Error {
268    fn from(e: anyhow::Error) -> Self {
269        Error::Operation(Cow::Owned(e.to_string()))
270    }
271}
272
273#[cfg(feature = "io")]
274impl From<dsq_io::Error> for Error {
275    fn from(e: dsq_io::Error) -> Self {
276        Error::Operation(Cow::Owned(e.to_string()))
277    }
278}
279
280impl From<dsq_formats::Error> for Error {
281    fn from(e: dsq_formats::Error) -> Self {
282        Error::Operation(Cow::Owned(e.to_string()))
283    }
284}
285
286// Helper functions for creating common errors
287impl Error {
288    /// Create an operation error with a custom message
289    pub fn operation(msg: impl Into<Cow<'static, str>>) -> Self {
290        Error::Operation(msg.into())
291    }
292
293    /// Create a configuration error with a custom message
294    pub fn config(msg: impl Into<String>) -> Self {
295        Error::Config(msg.into())
296    }
297
298    /// Create an operation error from shared utility
299    pub fn from_operation_error(msg: impl Into<Cow<'static, str>>) -> Self {
300        Error::Operation(msg.into())
301    }
302
303    /// Create a configuration error from shared utility
304    pub fn from_config_error(msg: impl Into<String>) -> Self {
305        Error::Config(msg.into())
306    }
307
308    /// Combine multiple errors into a single error
309    #[must_use]
310    pub fn combine(errors: Vec<Error>) -> Self {
311        match errors.len() {
312            0 => Error::operation(Cow::Borrowed("No errors")),
313            1 => errors.into_iter().next().unwrap(),
314            _ => Error::Multiple(errors),
315        }
316    }
317}
318
319impl FormatError {
320    /// Create an unknown format error
321    pub fn unknown(format: impl Into<String>) -> Self {
322        FormatError::Unknown(format.into())
323    }
324
325    /// Create a detection failed error
326    pub fn detection_failed(path: impl Into<String>) -> Self {
327        FormatError::DetectionFailed(path.into())
328    }
329}
330
331impl FilterError {
332    /// Create a parse error
333    pub fn parse(msg: impl Into<String>) -> Self {
334        FilterError::Parse(msg.into())
335    }
336
337    /// Create a compilation error
338    pub fn compile(msg: impl Into<String>) -> Self {
339        FilterError::Compile(msg.into())
340    }
341
342    /// Create a runtime error
343    pub fn runtime(msg: impl Into<String>) -> Self {
344        FilterError::Runtime(msg.into())
345    }
346}
347
348impl TypeError {
349    /// Create an invalid conversion error
350    pub fn invalid_conversion(from: impl Into<String>, to: impl Into<String>) -> Self {
351        TypeError::InvalidConversion {
352            from: from.into(),
353            to: to.into(),
354        }
355    }
356
357    /// Create an unsupported operation error
358    pub fn unsupported_operation(operation: impl Into<String>, typ: impl Into<String>) -> Self {
359        TypeError::UnsupportedOperation {
360            operation: operation.into(),
361            typ: typ.into(),
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_error_display() {
372        let err = Error::operation(Cow::Borrowed("test operation failed"));
373        assert_eq!(err.to_string(), "Operation error: test operation failed");
374
375        let err = FormatError::unknown("xyz");
376        assert_eq!(err.to_string(), "Unknown format: xyz");
377
378        let err = FilterError::parse("unexpected token");
379        assert_eq!(err.to_string(), "Parse error: unexpected token");
380
381        let err = TypeError::invalid_conversion("string", "number");
382        assert_eq!(err.to_string(), "Cannot convert from string to number");
383    }
384
385    #[test]
386    fn test_error_conversion() {
387        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
388        let err: Error = io_err.into();
389        assert!(matches!(err, Error::Io(_)));
390
391        let format_err = FormatError::unknown("test");
392        let err: Error = format_err.into();
393        assert!(matches!(err, Error::Format(_)));
394    }
395
396    #[test]
397    fn test_multiple_errors() {
398        let errors = vec![
399            Error::operation(Cow::Borrowed("error 1")),
400            Error::operation(Cow::Borrowed("error 2")),
401        ];
402        let combined = Error::combine(errors);
403        assert!(matches!(combined, Error::Multiple(_)));
404    }
405}