Skip to main content

dataset_core/
error.rs

1use ureq::Error as UreqError;
2use zip::result::ZipError;
3
4/// Specific kinds of data format errors that can occur during dataset parsing.
5///
6/// # Variants
7///
8/// - `CsvReadError` - Failed to read a CSV record.
9/// - `InvalidColumnCount` - The row has an unexpected number of columns.
10/// - `ParseFailed` - Failed to parse a field value into the target type.
11/// - `InvalidValue` - The field value is syntactically valid but semantically incorrect.
12/// - `LengthMismatch` - The total parsed data length does not match expected dimensions.
13/// - `EmptyDataset` - The dataset is empty.
14/// - `ArrayShapeError` - Failed to construct ndarray with the given shape and data.
15/// - `UnknownColumn` - The requested column is not in the dataset.
16/// - `ColumnTypeMismatch` - The column holds values of a type the operation cannot use.
17#[derive(Debug, thiserror::Error)]
18pub enum DataFormatErrorKind {
19    /// Failed to read a CSV record
20    #[error("[{dataset_name}] failed to read CSV record: {error}")]
21    CsvReadError {
22        /// Dataset identifier
23        dataset_name: String,
24        /// The underlying CSV error message
25        error: String,
26    },
27    /// The row has an unexpected number of columns
28    #[error(
29        "[{dataset_name}] invalid column count at line {line_num}: expected {expected}, got {actual}"
30    )]
31    InvalidColumnCount {
32        /// Dataset identifier
33        dataset_name: String,
34        /// Expected number of columns
35        expected: usize,
36        /// Actual number of columns found
37        actual: usize,
38        /// Line number (1-based)
39        line_num: usize,
40    },
41    /// Failed to parse a field value into the target type
42    #[error("[{dataset_name}] failed to parse `{field_name}` at line {line_num}: {error}")]
43    ParseFailed {
44        /// Dataset identifier
45        dataset_name: String,
46        /// Field name that failed to parse
47        field_name: String,
48        /// Line number (1-based)
49        line_num: usize,
50        /// The underlying parse error message
51        error: String,
52    },
53    /// The field value is syntactically valid but semantically incorrect
54    #[error("[{dataset_name}] invalid value for `{field_name}` at line {line_num}: `{value}`")]
55    InvalidValue {
56        /// Dataset identifier
57        dataset_name: String,
58        /// Field name with invalid value
59        field_name: String,
60        /// The invalid value
61        value: String,
62        /// Line number (1-based)
63        line_num: usize,
64    },
65    /// The total parsed data length does not match expected dimensions
66    #[error("[{dataset_name}] invalid `{field_name}` length: expected {expected}, got {actual}")]
67    LengthMismatch {
68        /// Dataset identifier
69        dataset_name: String,
70        /// Field name that failed the length check
71        field_name: String,
72        /// Expected length
73        expected: usize,
74        /// Actual length
75        actual: usize,
76    },
77    /// The dataset is empty
78    #[error("[{dataset_name}] is empty")]
79    EmptyDataset {
80        /// Dataset identifier
81        dataset_name: String,
82    },
83    /// Failed to construct ndarray with the given shape and data
84    #[error("[{dataset_name}] failed to build `{array_name}` array: {error}")]
85    ArrayShapeError {
86        /// Dataset identifier
87        dataset_name: String,
88        /// Array name that failed to build
89        array_name: String,
90        /// The underlying shape error message
91        error: String,
92    },
93    /// The requested column is not in the dataset
94    #[error("[{dataset_name}] no column named `{column_name}`")]
95    UnknownColumn {
96        /// Dataset identifier
97        dataset_name: String,
98        /// The requested column name
99        column_name: String,
100    },
101    /// The column holds values of a type the operation cannot use
102    #[error(
103        "[{dataset_name}] column `{column_name}` holds `{actual}` values, expected `{expected}`"
104    )]
105    ColumnTypeMismatch {
106        /// Dataset identifier
107        dataset_name: String,
108        /// The column with the unusable type
109        column_name: String,
110        /// The type the operation needs
111        expected: String,
112        /// The type the column holds
113        actual: String,
114    },
115}
116
117/// Error type used by dataset loading utilities.
118///
119/// # Variants
120///
121/// - `DownloadError` - The download step failed (network, invalid URL, or downloader configuration).
122/// - `ValidationError` - Downloaded file content failed integrity validation (SHA256 mismatch).
123/// - `UnzipError` - Failed to extract a zip archive.
124/// - `IoError` - A standard I/O operation failed, such as reading a directory or opening or removing a file.
125/// - `DataFormatError` - The dataset content was not in the expected format.
126#[derive(Debug, thiserror::Error)]
127pub enum DatasetError {
128    #[error("Download error: {0}")]
129    DownloadError(#[from] UreqError),
130
131    #[error("Validation error: {0}")]
132    ValidationError(String),
133
134    #[error("Unzip error: {0}")]
135    UnzipError(#[from] ZipError),
136
137    #[error("I/O error: {0}")]
138    IoError(#[from] std::io::Error),
139
140    #[error("Data format error: {0}")]
141    DataFormatError(#[from] DataFormatErrorKind),
142}
143
144impl DatasetError {
145    /// Creates a standard SHA256 validation failure error message for a file.
146    ///
147    /// # Parameters
148    ///
149    /// - `dataset_name` - The dataset identifier used in the error prefix.
150    /// - `file_name` - The dataset file name that failed checksum validation.
151    ///
152    /// # Returns
153    ///
154    /// - `DatasetError::ValidationError` - A variant of `DatasetError` that contains the unified SHA256 failure message.
155    pub fn sha256_validation_failed(dataset_name: &str, file_name: &str) -> Self {
156        Self::ValidationError(format!(
157            "[{}] SHA256 validation failed for file `{}`",
158            dataset_name, file_name
159        ))
160    }
161
162    /// Creates a CSV read error.
163    ///
164    /// # Parameters
165    ///
166    /// - `dataset_name` - The dataset identifier.
167    /// - `error` - The underlying CSV error.
168    ///
169    /// # Returns
170    ///
171    /// - `DatasetError::DataFormatError(DataFormatErrorKind::CsvReadError)` - A variant of `DatasetError` describing the CSV read error.
172    pub fn csv_read_error(dataset_name: &str, error: impl std::fmt::Display) -> Self {
173        Self::DataFormatError(DataFormatErrorKind::CsvReadError {
174            dataset_name: dataset_name.to_string(),
175            error: error.to_string(),
176        })
177    }
178
179    /// Creates a unified invalid-column-count data format error.
180    ///
181    /// # Parameters
182    ///
183    /// - `dataset_name` - The dataset identifier used in the error prefix.
184    /// - `expected` - The expected number of columns.
185    /// - `actual` - The actual number of columns found.
186    /// - `line_num` - The line number (1-based) where the error occurred.
187    ///
188    /// # Returns
189    ///
190    /// - `DatasetError::DataFormatError(DataFormatErrorKind::InvalidColumnCount)` - A variant of `DatasetError` describing the column count mismatch.
191    pub fn invalid_column_count(
192        dataset_name: &str,
193        expected: usize,
194        actual: usize,
195        line_num: usize,
196    ) -> Self {
197        Self::DataFormatError(DataFormatErrorKind::InvalidColumnCount {
198            dataset_name: dataset_name.to_string(),
199            expected,
200            actual,
201            line_num,
202        })
203    }
204
205    /// Creates a unified parse failure data format error.
206    ///
207    /// # Parameters
208    ///
209    /// - `dataset_name` - The dataset identifier.
210    /// - `field_name` - The logical field name that failed to parse.
211    /// - `line_num` - The line number (1-based) where the error occurred.
212    /// - `line` - The original input line where parsing failed.
213    /// - `err` - The underlying parser error detail.
214    ///
215    /// # Returns
216    ///
217    /// - `DatasetError::DataFormatError(DataFormatErrorKind::ParseFailed)` - A variant of `DatasetError` describing the parse failure.
218    pub fn parse_failed(
219        dataset_name: &str,
220        field_name: &str,
221        line_num: usize,
222        err: impl std::fmt::Display,
223    ) -> Self {
224        Self::DataFormatError(DataFormatErrorKind::ParseFailed {
225            dataset_name: dataset_name.to_string(),
226            field_name: field_name.to_string(),
227            line_num,
228            error: err.to_string(),
229        })
230    }
231
232    /// Creates a unified invalid-field-value data format error.
233    ///
234    /// # Parameters
235    ///
236    /// - `dataset_name` - The dataset identifier.
237    /// - `field_name` - The logical field name with an invalid value.
238    /// - `value` - The invalid raw value.
239    /// - `line_num` - The line number (1-based) where the error occurred.
240    ///
241    /// # Returns
242    ///
243    /// - `DatasetError::DataFormatError(DataFormatErrorKind::InvalidValue)` - A variant of `DatasetError` describing the invalid value.
244    pub fn invalid_value(
245        dataset_name: &str,
246        field_name: &str,
247        value: &str,
248        line_num: usize,
249    ) -> Self {
250        Self::DataFormatError(DataFormatErrorKind::InvalidValue {
251            dataset_name: dataset_name.to_string(),
252            field_name: field_name.to_string(),
253            value: value.to_string(),
254            line_num,
255        })
256    }
257
258    /// Creates a unified vector/row length mismatch data format error.
259    ///
260    /// # Parameters
261    ///
262    /// - `dataset_name` - The dataset identifier.
263    /// - `field_name` - The logical field name that this check validates.
264    /// - `expected` - The expected length.
265    /// - `actual` - The actual length.
266    ///
267    /// # Returns
268    ///
269    /// - `DatasetError::DataFormatError(DataFormatErrorKind::LengthMismatch)` - A variant of `DatasetError` describing the length mismatch.
270    pub fn length_mismatch(
271        dataset_name: &str,
272        field_name: &str,
273        expected: usize,
274        actual: usize,
275    ) -> Self {
276        Self::DataFormatError(DataFormatErrorKind::LengthMismatch {
277            dataset_name: dataset_name.to_string(),
278            field_name: field_name.to_string(),
279            expected,
280            actual,
281        })
282    }
283
284    /// Creates a unified ndarray shape construction data format error.
285    ///
286    /// # Parameters
287    ///
288    /// - `dataset_name` - The dataset identifier.
289    /// - `array_name` - The logical array name that failed to build.
290    /// - `err` - The underlying ndarray shape construction error detail.
291    ///
292    /// # Returns
293    ///
294    /// - `DatasetError::DataFormatError(DataFormatErrorKind::ArrayShapeError)` - A variant of `DatasetError` describing the array shape failure.
295    pub fn array_shape_error(
296        dataset_name: &str,
297        array_name: &str,
298        err: impl std::fmt::Display,
299    ) -> Self {
300        Self::DataFormatError(DataFormatErrorKind::ArrayShapeError {
301            dataset_name: dataset_name.to_string(),
302            array_name: array_name.to_string(),
303            error: err.to_string(),
304        })
305    }
306
307    /// Creates an empty dataset error.
308    ///
309    /// # Parameters
310    ///
311    /// - `dataset_name` - The dataset identifier.
312    ///
313    /// # Returns
314    ///
315    /// - `DatasetError::DataFormatError(DataFormatErrorKind::EmptyDataset)` - A variant of `DatasetError` indicating the dataset is empty.
316    pub fn empty_dataset(dataset_name: &str) -> Self {
317        Self::DataFormatError(DataFormatErrorKind::EmptyDataset {
318            dataset_name: dataset_name.to_string(),
319        })
320    }
321
322    /// Creates an unknown column error.
323    ///
324    /// # Parameters
325    ///
326    /// - `dataset_name` - The dataset identifier.
327    /// - `column_name` - The requested column name.
328    ///
329    /// # Returns
330    ///
331    /// - `DatasetError::DataFormatError(DataFormatErrorKind::UnknownColumn)` - A variant of `DatasetError` naming the column the dataset does not hold.
332    pub fn unknown_column(dataset_name: &str, column_name: &str) -> Self {
333        Self::DataFormatError(DataFormatErrorKind::UnknownColumn {
334            dataset_name: dataset_name.to_string(),
335            column_name: column_name.to_string(),
336        })
337    }
338
339    /// Creates a column type mismatch error.
340    ///
341    /// # Parameters
342    ///
343    /// - `dataset_name` - The dataset identifier.
344    /// - `column_name` - The column with the unusable type.
345    /// - `expected` - The type the operation needs.
346    /// - `actual` - The type the column holds.
347    ///
348    /// # Returns
349    ///
350    /// - `DatasetError::DataFormatError(DataFormatErrorKind::ColumnTypeMismatch)` - A variant of `DatasetError` describing the type mismatch.
351    pub fn column_type_mismatch(
352        dataset_name: &str,
353        column_name: &str,
354        expected: &str,
355        actual: &str,
356    ) -> Self {
357        Self::DataFormatError(DataFormatErrorKind::ColumnTypeMismatch {
358            dataset_name: dataset_name.to_string(),
359            column_name: column_name.to_string(),
360            expected: expected.to_string(),
361            actual: actual.to_string(),
362        })
363    }
364}