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 doesn't match expected dimensions.
13/// - `EmptyDataset` - The dataset is empty.
14/// - `ArrayShapeError` - Failed to construct ndarray with the given shape and data.
15#[derive(Debug, thiserror::Error)]
16pub enum DataFormatErrorKind {
17    /// Failed to read a CSV record
18    #[error("[{dataset_name}] failed to read CSV record: {error}")]
19    CsvReadError {
20        /// Dataset identifier
21        dataset_name: String,
22        /// The underlying CSV error message
23        error: String,
24    },
25    /// The row has an unexpected number of columns
26    #[error(
27        "[{dataset_name}] invalid column count at line {line_num}: expected {expected}, got {actual}"
28    )]
29    InvalidColumnCount {
30        /// Dataset identifier
31        dataset_name: String,
32        /// Expected number of columns
33        expected: usize,
34        /// Actual number of columns found
35        actual: usize,
36        /// Line number (1-based)
37        line_num: usize,
38    },
39    /// Failed to parse a field value into the target type
40    #[error("[{dataset_name}] failed to parse `{field_name}` at line {line_num}: {error}")]
41    ParseFailed {
42        /// Dataset identifier
43        dataset_name: String,
44        /// Field name that failed to parse
45        field_name: String,
46        /// Line number (1-based)
47        line_num: usize,
48        /// The underlying parse error message
49        error: String,
50    },
51    /// The field value is syntactically valid but semantically incorrect
52    #[error("[{dataset_name}] invalid value for `{field_name}` at line {line_num}: `{value}`")]
53    InvalidValue {
54        /// Dataset identifier
55        dataset_name: String,
56        /// Field name with invalid value
57        field_name: String,
58        /// The invalid value
59        value: String,
60        /// Line number (1-based)
61        line_num: usize,
62    },
63    /// The total parsed data length doesn't match expected dimensions
64    #[error("[{dataset_name}] invalid `{field_name}` length: expected {expected}, got {actual}")]
65    LengthMismatch {
66        /// Dataset identifier
67        dataset_name: String,
68        /// Field name whose length is being validated
69        field_name: String,
70        /// Expected length
71        expected: usize,
72        /// Actual length
73        actual: usize,
74    },
75    /// The dataset is empty
76    #[error("[{dataset_name}] is empty")]
77    EmptyDataset {
78        /// Dataset identifier
79        dataset_name: String,
80    },
81    /// Failed to construct ndarray with the given shape and data
82    #[error("[{dataset_name}] failed to build `{array_name}` array: {error}")]
83    ArrayShapeError {
84        /// Dataset identifier
85        dataset_name: String,
86        /// Array name that failed to build
87        array_name: String,
88        /// The underlying shape error message
89        error: String,
90    },
91}
92
93/// Error type used by dataset loading utilities.
94///
95/// # Variants
96///
97/// - `DownloadError` - The download step failed (network, invalid URL, or downloader configuration).
98/// - `ValidationError` - Downloaded file content failed integrity validation (SHA256 mismatch).
99/// - `UnzipError` - Extracting a zip archive failed.
100/// - `IoError` - A standard I/O operation failed (reading directories, opening/removing files, etc.).
101/// - `DataFormatError` - The dataset content was not in the expected format.
102#[derive(Debug, thiserror::Error)]
103pub enum DatasetError {
104    #[error("Download error: {0}")]
105    DownloadError(#[from] UreqError),
106
107    #[error("Validation error: {0}")]
108    ValidationError(String),
109
110    #[error("Unzip error: {0}")]
111    UnzipError(#[from] ZipError),
112
113    #[error("I/O error: {0}")]
114    IoError(#[from] std::io::Error),
115
116    #[error("Data format error: {0}")]
117    DataFormatError(#[from] DataFormatErrorKind),
118}
119
120impl DatasetError {
121    /// Creates a standard SHA256 validation failure error message for a file.
122    ///
123    /// # Parameters
124    ///
125    /// - `dataset_name` - The dataset identifier used in the error prefix.
126    /// - `file_name` - The dataset file name that failed checksum validation.
127    ///
128    /// # Returns
129    ///
130    /// - `DatasetError::ValidationError` - A variant of `DatasetError` that contains the unified SHA256 failure message.
131    pub fn sha256_validation_failed(dataset_name: &str, file_name: &str) -> Self {
132        Self::ValidationError(format!(
133            "[{}] SHA256 validation failed for file `{}`",
134            dataset_name, file_name
135        ))
136    }
137
138    /// Creates a CSV read error.
139    ///
140    /// # Parameters
141    ///
142    /// - `dataset_name` - The dataset identifier.
143    /// - `error` - The underlying CSV error.
144    ///
145    /// # Returns
146    ///
147    /// - `DatasetError::DataFormatError(DataFormatErrorKind::CsvReadError)` - A variant of `DatasetError` describing the CSV read error.
148    pub fn csv_read_error(dataset_name: &str, error: impl std::fmt::Display) -> Self {
149        Self::DataFormatError(DataFormatErrorKind::CsvReadError {
150            dataset_name: dataset_name.to_string(),
151            error: error.to_string(),
152        })
153    }
154
155    /// Creates a unified invalid-column-count data format error.
156    ///
157    /// # Parameters
158    ///
159    /// - `dataset_name` - The dataset identifier used in the error prefix.
160    /// - `expected` - The expected number of columns.
161    /// - `actual` - The actual number of columns found.
162    /// - `line_num` - The line number (1-based) where the error occurred.
163    ///
164    /// # Returns
165    ///
166    /// - `DatasetError::DataFormatError(DataFormatErrorKind::InvalidColumnCount)` - A variant of `DatasetError` describing the column count mismatch.
167    pub fn invalid_column_count(
168        dataset_name: &str,
169        expected: usize,
170        actual: usize,
171        line_num: usize,
172    ) -> Self {
173        Self::DataFormatError(DataFormatErrorKind::InvalidColumnCount {
174            dataset_name: dataset_name.to_string(),
175            expected,
176            actual,
177            line_num,
178        })
179    }
180
181    /// Creates a unified parse failure data format error.
182    ///
183    /// # Parameters
184    ///
185    /// - `dataset_name` - The dataset identifier.
186    /// - `field_name` - The logical field name that failed to parse.
187    /// - `line_num` - The line number (1-based) where the error occurred.
188    /// - `line` - The original input line where parsing failed.
189    /// - `err` - The underlying parser error detail.
190    ///
191    /// # Returns
192    ///
193    /// - `DatasetError::DataFormatError(DataFormatErrorKind::ParseFailed)` - A variant of `DatasetError` describing the parse failure.
194    pub fn parse_failed(
195        dataset_name: &str,
196        field_name: &str,
197        line_num: usize,
198        err: impl std::fmt::Display,
199    ) -> Self {
200        Self::DataFormatError(DataFormatErrorKind::ParseFailed {
201            dataset_name: dataset_name.to_string(),
202            field_name: field_name.to_string(),
203            line_num,
204            error: err.to_string(),
205        })
206    }
207
208    /// Creates a unified invalid-field-value data format error.
209    ///
210    /// # Parameters
211    ///
212    /// - `dataset_name` - The dataset identifier.
213    /// - `field_name` - The logical field name with an invalid value.
214    /// - `value` - The invalid raw value.
215    /// - `line_num` - The line number (1-based) where the error occurred.
216    ///
217    /// # Returns
218    ///
219    /// - `DatasetError::DataFormatError(DataFormatErrorKind::InvalidValue)` - A variant of `DatasetError` describing the invalid value.
220    pub fn invalid_value(
221        dataset_name: &str,
222        field_name: &str,
223        value: &str,
224        line_num: usize,
225    ) -> Self {
226        Self::DataFormatError(DataFormatErrorKind::InvalidValue {
227            dataset_name: dataset_name.to_string(),
228            field_name: field_name.to_string(),
229            value: value.to_string(),
230            line_num,
231        })
232    }
233
234    /// Creates a unified vector/row length mismatch data format error.
235    ///
236    /// # Parameters
237    ///
238    /// - `dataset_name` - The dataset identifier.
239    /// - `field_name` - The logical field name whose length is being validated.
240    /// - `expected` - The expected length.
241    /// - `actual` - The actual length.
242    ///
243    /// # Returns
244    ///
245    /// - `DatasetError::DataFormatError(DataFormatErrorKind::LengthMismatch)` - A variant of `DatasetError` describing the length mismatch.
246    pub fn length_mismatch(
247        dataset_name: &str,
248        field_name: &str,
249        expected: usize,
250        actual: usize,
251    ) -> Self {
252        Self::DataFormatError(DataFormatErrorKind::LengthMismatch {
253            dataset_name: dataset_name.to_string(),
254            field_name: field_name.to_string(),
255            expected,
256            actual,
257        })
258    }
259
260    /// Creates a unified ndarray shape construction data format error.
261    ///
262    /// # Parameters
263    ///
264    /// - `dataset_name` - The dataset identifier.
265    /// - `array_name` - The logical array name that failed to build.
266    /// - `err` - The underlying ndarray shape construction error detail.
267    ///
268    /// # Returns
269    ///
270    /// - `DatasetError::DataFormatError(DataFormatErrorKind::ArrayShapeError)` - A variant of `DatasetError` describing the array shape failure.
271    pub fn array_shape_error(
272        dataset_name: &str,
273        array_name: &str,
274        err: impl std::fmt::Display,
275    ) -> Self {
276        Self::DataFormatError(DataFormatErrorKind::ArrayShapeError {
277            dataset_name: dataset_name.to_string(),
278            array_name: array_name.to_string(),
279            error: err.to_string(),
280        })
281    }
282
283    /// Creates an empty dataset error.
284    ///
285    /// # Parameters
286    ///
287    /// - `dataset_name` - The dataset identifier.
288    ///
289    /// # Returns
290    ///
291    /// - `DatasetError::DataFormatError(DataFormatErrorKind::EmptyDataset)` - A variant of `DatasetError` indicating the dataset is empty.
292    pub fn empty_dataset(dataset_name: &str) -> Self {
293        Self::DataFormatError(DataFormatErrorKind::EmptyDataset {
294            dataset_name: dataset_name.to_string(),
295        })
296    }
297}