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}