Skip to main content

plotlars_core/io/
error.rs

1use std::fmt;
2
3/// Unified error type for plotlars I/O and plot construction failures.
4///
5/// This enum is `#[non_exhaustive]` -- new variants may be added in minor
6/// releases without breaking downstream `match` arms that include a wildcard.
7///
8/// # Example
9///
10/// ```rust,no_run
11/// use plotlars_core::io::{CsvReader, PlotlarsError};
12///
13/// fn main() -> Result<(), PlotlarsError> {
14///     let df = CsvReader::new("data/penguins.csv").finish()?;
15///     Ok(())
16/// }
17/// ```
18#[derive(Debug)]
19#[non_exhaustive]
20pub enum PlotlarsError {
21    /// File not found, permission denied, or other I/O error.
22    Io {
23        path: String,
24        source: std::io::Error,
25    },
26
27    /// CSV parsing failure.
28    CsvParse {
29        path: String,
30        source: Box<dyn std::error::Error + Send + Sync>,
31    },
32
33    /// Parquet parsing failure.
34    ParquetParse {
35        path: String,
36        source: Box<dyn std::error::Error + Send + Sync>,
37    },
38
39    /// JSON (NDJSON) parsing failure.
40    #[cfg(feature = "format-json")]
41    JsonParse {
42        path: String,
43        source: Box<dyn std::error::Error + Send + Sync>,
44    },
45
46    /// Excel parsing failure.
47    #[cfg(feature = "format-excel")]
48    ExcelParse {
49        path: String,
50        source: Box<dyn std::error::Error + Send + Sync>,
51    },
52
53    /// A required column was not found in the DataFrame.
54    ColumnNotFound {
55        column: String,
56        available: Vec<String>,
57    },
58
59    /// A column's data type did not match what the plot expected.
60    TypeMismatch {
61        column: String,
62        expected: String,
63        actual: String,
64    },
65
66    /// Plot construction failure.
67    PlotBuild { message: String },
68}
69
70impl fmt::Display for PlotlarsError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::Io { path, source } => {
74                write!(f, "I/O error reading '{}': {}", path, source)
75            }
76            Self::CsvParse { path, source } => {
77                write!(f, "failed to parse CSV '{}': {}", path, source)
78            }
79            Self::ParquetParse { path, source } => {
80                write!(f, "failed to parse Parquet '{}': {}", path, source)
81            }
82            #[cfg(feature = "format-json")]
83            Self::JsonParse { path, source } => {
84                write!(f, "failed to parse JSON '{}': {}", path, source)
85            }
86            #[cfg(feature = "format-excel")]
87            Self::ExcelParse { path, source } => {
88                write!(f, "failed to parse Excel '{}': {}", path, source)
89            }
90            Self::ColumnNotFound { column, available } => {
91                write!(
92                    f,
93                    "column '{}' not found; available columns: [{}]",
94                    column,
95                    available.join(", ")
96                )
97            }
98            Self::TypeMismatch {
99                column,
100                expected,
101                actual,
102            } => {
103                write!(
104                    f,
105                    "column '{}': expected type {}, found {}",
106                    column, expected, actual
107                )
108            }
109            Self::PlotBuild { message } => {
110                write!(f, "plot construction error: {}", message)
111            }
112        }
113    }
114}
115
116impl std::error::Error for PlotlarsError {
117    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118        match self {
119            Self::Io { source, .. } => Some(source),
120            Self::CsvParse { source, .. } => Some(source.as_ref()),
121            Self::ParquetParse { source, .. } => Some(source.as_ref()),
122            #[cfg(feature = "format-json")]
123            Self::JsonParse { source, .. } => Some(source.as_ref()),
124            #[cfg(feature = "format-excel")]
125            Self::ExcelParse { source, .. } => Some(source.as_ref()),
126            Self::ColumnNotFound { .. } | Self::TypeMismatch { .. } | Self::PlotBuild { .. } => {
127                None
128            }
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn display_io_error() {
139        let err = PlotlarsError::Io {
140            path: "test.csv".to_string(),
141            source: std::io::Error::new(std::io::ErrorKind::NotFound, "not found"),
142        };
143        assert!(err.to_string().contains("test.csv"));
144        assert!(err.to_string().contains("not found"));
145    }
146
147    #[test]
148    fn display_csv_parse_error() {
149        let err = PlotlarsError::CsvParse {
150            path: "bad.csv".to_string(),
151            source: "invalid data".into(),
152        };
153        assert!(err.to_string().contains("bad.csv"));
154    }
155
156    #[test]
157    fn display_column_not_found() {
158        let err = PlotlarsError::ColumnNotFound {
159            column: "missing".to_string(),
160            available: vec!["a".to_string(), "b".to_string()],
161        };
162        let msg = err.to_string();
163        assert!(msg.contains("missing"));
164        assert!(msg.contains("a, b"));
165    }
166
167    #[test]
168    fn display_type_mismatch() {
169        let err = PlotlarsError::TypeMismatch {
170            column: "x".to_string(),
171            expected: "Float64".to_string(),
172            actual: "String".to_string(),
173        };
174        let msg = err.to_string();
175        assert!(msg.contains("Float64"));
176        assert!(msg.contains("String"));
177    }
178
179    #[test]
180    fn error_source_chain() {
181        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
182        let err = PlotlarsError::Io {
183            path: "x".to_string(),
184            source: io_err,
185        };
186        assert!(std::error::Error::source(&err).is_some());
187
188        let err = PlotlarsError::ColumnNotFound {
189            column: "x".to_string(),
190            available: vec![],
191        };
192        assert!(std::error::Error::source(&err).is_none());
193    }
194}