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}