dsq_formats/
error.rs

1use std::borrow::Cow;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6/// Result type alias for dsq operations
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Main error type for dsq operations
10#[derive(Debug)]
11pub enum Error {
12    /// I/O errors (file operations, etc.)
13    Io(io::Error),
14
15    /// Polars errors (`DataFrame` operations)
16    Polars(polars::error::PolarsError),
17
18    /// JSON parsing errors
19    Json(serde_json::Error),
20
21    /// Format detection or parsing errors
22    Format(FormatError),
23
24    /// General operation errors
25    Operation(Cow<'static, str>),
26
27    /// Configuration errors
28    Config(String),
29
30    /// Multiple errors collected during processing
31    Multiple(Vec<Error>),
32}
33
34/// Errors related to file format handling
35#[derive(Debug, Clone)]
36pub enum FormatError {
37    /// Unknown or unsupported file format
38    Unknown(String),
39
40    /// Failed to detect format from file extension
41    DetectionFailed(String),
42
43    /// Format is supported but specific feature is not
44    UnsupportedFeature(String),
45
46    /// Schema mismatch between expected and actual
47    SchemaMismatch {
48        /// Expected schema
49        expected: String,
50        /// Actual schema
51        actual: String,
52    },
53
54    /// Invalid format-specific options
55    InvalidOption(String),
56
57    /// Serialization/deserialization errors
58    SerializationError(String),
59}
60
61impl fmt::Display for Error {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Error::Io(e) => write!(f, "I/O error: {e}"),
65            Error::Polars(e) => write!(f, "DataFrame error: {}", e),
66            Error::Json(e) => write!(f, "JSON error: {}", e),
67            Error::Format(e) => write!(f, "Format error: {}", e),
68            Error::Operation(msg) => write!(f, "Operation error: {}", msg),
69            Error::Config(msg) => write!(f, "Configuration error: {}", msg),
70            Error::Multiple(errors) => {
71                write!(f, "Multiple errors occurred:")?;
72                for (i, e) in errors.iter().enumerate() {
73                    write!(f, "\n  {}. {}", i + 1, e)?;
74                }
75                Ok(())
76            }
77        }
78    }
79}
80
81impl fmt::Display for FormatError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            FormatError::Unknown(format) => {
85                write!(f, "Unknown format: {}", format)
86            }
87            FormatError::DetectionFailed(path) => {
88                write!(f, "Failed to detect format for: {}", path)
89            }
90            FormatError::UnsupportedFeature(feature) => {
91                write!(f, "Unsupported feature: {}", feature)
92            }
93            FormatError::SchemaMismatch { expected, actual } => {
94                write!(f, "Schema mismatch: expected {}, got {}", expected, actual)
95            }
96            FormatError::InvalidOption(option) => {
97                write!(f, "Invalid format option: {}", option)
98            }
99            FormatError::SerializationError(msg) => {
100                write!(f, "Serialization error: {}", msg)
101            }
102        }
103    }
104}
105
106impl StdError for Error {
107    fn source(&self) -> Option<&(dyn StdError + 'static)> {
108        match self {
109            Error::Io(e) => Some(e),
110            Error::Polars(e) => Some(e),
111            Error::Json(e) => Some(e),
112            _ => None,
113        }
114    }
115}
116
117impl StdError for FormatError {}
118
119// Conversion implementations
120impl From<io::Error> for Error {
121    fn from(e: io::Error) -> Self {
122        Error::Io(e)
123    }
124}
125
126impl From<polars::error::PolarsError> for Error {
127    fn from(e: polars::error::PolarsError) -> Self {
128        Error::Polars(e)
129    }
130}
131
132impl From<serde_json::Error> for Error {
133    fn from(e: serde_json::Error) -> Self {
134        Error::Json(e)
135    }
136}
137
138impl From<apache_avro::Error> for Error {
139    fn from(e: apache_avro::Error) -> Self {
140        Error::Format(FormatError::SerializationError(e.to_string()))
141    }
142}
143
144impl From<FormatError> for Error {
145    fn from(e: FormatError) -> Self {
146        Error::Format(e)
147    }
148}
149
150impl From<anyhow::Error> for Error {
151    fn from(e: anyhow::Error) -> Self {
152        Error::Operation(Cow::Owned(e.to_string()))
153    }
154}
155
156// Helper functions for creating common errors
157impl Error {
158    /// Create an operation error with a custom message
159    pub fn operation(msg: impl Into<Cow<'static, str>>) -> Self {
160        Error::Operation(msg.into())
161    }
162
163    /// Create a configuration error with a custom message
164    pub fn config(msg: impl Into<String>) -> Self {
165        Error::Config(msg.into())
166    }
167
168    /// Combine multiple errors into a single error
169    pub fn combine(errors: Vec<Error>) -> Self {
170        match errors.len() {
171            0 => Error::operation(Cow::Borrowed("No errors")),
172            1 => errors.into_iter().next().unwrap(),
173            _ => Error::Multiple(errors),
174        }
175    }
176}
177
178impl FormatError {
179    /// Create an unknown format error
180    pub fn unknown(format: impl Into<String>) -> Self {
181        FormatError::Unknown(format.into())
182    }
183
184    /// Create a detection failed error
185    pub fn detection_failed(path: impl Into<String>) -> Self {
186        FormatError::DetectionFailed(path.into())
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_error_display() {
196        // Test Error variants
197        let err = Error::operation(Cow::Borrowed("test operation failed"));
198        assert_eq!(err.to_string(), "Operation error: test operation failed");
199
200        let err = Error::config("test config failed".to_string());
201        assert_eq!(err.to_string(), "Configuration error: test config failed");
202
203        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
204        let err = Error::Io(io_err);
205        assert_eq!(err.to_string(), "I/O error: file not found");
206
207        // For Polars and Json, we can't easily test exact string without creating real errors
208        // But we can test the prefix
209        let polars_err = polars::error::PolarsError::from(io::Error::other("test polars error"));
210        let err = Error::Polars(polars_err);
211        assert!(err.to_string().starts_with("DataFrame error:"));
212
213        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
214        let err = Error::Json(json_err);
215        assert!(err.to_string().starts_with("JSON error:"));
216
217        let format_err = FormatError::unknown("xyz");
218        let err = Error::Format(format_err);
219        assert_eq!(err.to_string(), "Format error: Unknown format: xyz");
220
221        // Test Multiple
222        let errors = vec![
223            Error::operation(Cow::Borrowed("error 1")),
224            Error::operation(Cow::Borrowed("error 2")),
225        ];
226        let combined = Error::combine(errors);
227        let display = combined.to_string();
228        assert!(display.starts_with("Multiple errors occurred:"));
229        assert!(display.contains("1. Operation error: error 1"));
230        assert!(display.contains("2. Operation error: error 2"));
231    }
232
233    #[test]
234    fn test_format_error_display() {
235        let err = FormatError::Unknown("xyz".to_string());
236        assert_eq!(err.to_string(), "Unknown format: xyz");
237
238        let err = FormatError::DetectionFailed("path/to/file".to_string());
239        assert_eq!(err.to_string(), "Failed to detect format for: path/to/file");
240
241        let err = FormatError::UnsupportedFeature("streaming".to_string());
242        assert_eq!(err.to_string(), "Unsupported feature: streaming");
243
244        let err = FormatError::SchemaMismatch {
245            expected: "schema1".to_string(),
246            actual: "schema2".to_string(),
247        };
248        assert_eq!(
249            err.to_string(),
250            "Schema mismatch: expected schema1, got schema2"
251        );
252
253        let err = FormatError::InvalidOption("option=value".to_string());
254        assert_eq!(err.to_string(), "Invalid format option: option=value");
255
256        let err = FormatError::SerializationError("failed to serialize".to_string());
257        assert_eq!(err.to_string(), "Serialization error: failed to serialize");
258    }
259
260    #[test]
261    fn test_error_conversion() {
262        // Io
263        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
264        let err: Error = io_err.into();
265        assert!(matches!(err, Error::Io(_)));
266
267        // Polars
268        let polars_err = polars::error::PolarsError::from(io::Error::other("test"));
269        let err: Error = polars_err.into();
270        assert!(matches!(err, Error::Polars(_)));
271
272        // Json
273        let json_err = serde_json::from_str::<serde_json::Value>("{").unwrap_err();
274        let err: Error = json_err.into();
275        assert!(matches!(err, Error::Json(_)));
276
277        // Format
278        let format_err = FormatError::unknown("test");
279        let err: Error = format_err.into();
280        assert!(matches!(err, Error::Format(_)));
281
282        // Anyhow (if available, but since it's in impl, assume it's there)
283        // Note: anyhow::Error might not be imported, but the impl is there
284    }
285
286    #[test]
287    fn test_error_source() {
288        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
289        let err = Error::Io(io_err);
290        assert!(err.source().is_some());
291
292        let polars_err = polars::error::PolarsError::from(io::Error::other("test"));
293        let err = Error::Polars(polars_err);
294        assert!(err.source().is_some());
295
296        let json_err = serde_json::from_str::<serde_json::Value>("{").unwrap_err();
297        let err = Error::Json(json_err);
298        assert!(err.source().is_some());
299
300        let err = Error::operation("test");
301        assert!(err.source().is_none());
302
303        let err = Error::config("test");
304        assert!(err.source().is_none());
305
306        let format_err = FormatError::unknown("test");
307        let err = Error::Format(format_err);
308        assert!(err.source().is_none());
309
310        let errors = vec![Error::operation("1"), Error::operation("2")];
311        let err = Error::combine(errors);
312        assert!(err.source().is_none());
313    }
314
315    #[test]
316    fn test_helper_functions() {
317        // Error::operation
318        let err = Error::operation("test msg");
319        assert!(matches!(err, Error::Operation(_)));
320        assert_eq!(err.to_string(), "Operation error: test msg");
321
322        let err = Error::operation(Cow::Owned("owned".to_string()));
323        assert!(matches!(err, Error::Operation(_)));
324
325        // Error::config
326        let err = Error::config("config msg");
327        assert!(matches!(err, Error::Config(_)));
328        assert_eq!(err.to_string(), "Configuration error: config msg");
329
330        // FormatError::unknown
331        let err = FormatError::unknown("fmt");
332        assert!(matches!(err, FormatError::Unknown(_)));
333        assert_eq!(err.to_string(), "Unknown format: fmt");
334
335        // FormatError::detection_failed
336        let err = FormatError::detection_failed("path");
337        assert!(matches!(err, FormatError::DetectionFailed(_)));
338        assert_eq!(err.to_string(), "Failed to detect format for: path");
339    }
340
341    #[test]
342    fn test_error_combine() {
343        // Empty
344        let combined = Error::combine(vec![]);
345        assert!(matches!(combined, Error::Operation(_)));
346        assert_eq!(combined.to_string(), "Operation error: No errors");
347
348        // Single
349        let single = Error::operation("single");
350        let combined = Error::combine(vec![single]);
351        assert!(matches!(combined, Error::Operation(_)));
352        assert_eq!(combined.to_string(), "Operation error: single".to_string());
353
354        // Multiple
355        let errors = vec![
356            Error::operation("error 1"),
357            Error::operation("error 2"),
358            Error::config("config error"),
359        ];
360        let combined = Error::combine(errors);
361        assert!(matches!(combined, Error::Multiple(_)));
362        let display = combined.to_string();
363        assert!(display.contains("Multiple errors occurred:"));
364        assert!(display.contains("1. Operation error: error 1"));
365        assert!(display.contains("2. Operation error: error 2"));
366        assert!(display.contains("3. Configuration error: config error"));
367    }
368
369    #[test]
370    fn test_format_error_clone() {
371        let err = FormatError::Unknown("test".to_string());
372        let cloned = err.clone();
373        assert_eq!(err.to_string(), cloned.to_string());
374    }
375}