1use std::path::PathBuf;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12 #[error("I/O error at {path:?}: {source}")]
14 Io {
15 path: Option<PathBuf>,
17 #[source]
19 source: std::io::Error,
20 },
21
22 #[error("Arrow error: {0}")]
24 Arrow(#[from] arrow::error::ArrowError),
25
26 #[error("Parquet error: {0}")]
28 Parquet(#[from] parquet::errors::ParquetError),
29
30 #[error("Index {index} out of bounds for dataset with {len} rows")]
32 IndexOutOfBounds {
33 index: usize,
35 len: usize,
37 },
38
39 #[error("Column '{name}' not found in schema")]
41 ColumnNotFound {
42 name: String,
44 },
45
46 #[error("Invalid configuration: {message}")]
48 InvalidConfig {
49 message: String,
51 },
52
53 #[error("Unsupported format: {format}")]
55 UnsupportedFormat {
56 format: String,
58 },
59
60 #[error("Dataset is empty")]
62 EmptyDataset,
63
64 #[error("Schema mismatch: {message}")]
66 SchemaMismatch {
67 message: String,
69 },
70
71 #[error("Storage backend error: {message}")]
73 Storage {
74 message: String,
76 },
77
78 #[error("Transform error: {message}")]
80 Transform {
81 message: String,
83 },
84
85 #[error("Parse error: {message}")]
87 Parse {
88 message: String,
90 },
91
92 #[error("Data error: {message}")]
94 Data {
95 message: String,
97 },
98
99 #[error("Format error: {0}")]
101 Format(String),
102
103 #[error("Checksum mismatch: expected {expected:08X}, got {actual:08X}")]
105 ChecksumMismatch {
106 expected: u32,
108 actual: u32,
110 },
111
112 #[error("License expired at {expired_at} (current time: {current_time})")]
114 LicenseExpired {
115 expired_at: u64,
117 current_time: u64,
119 },
120
121 #[error("Signature verification failed")]
123 SignatureInvalid,
124
125 #[error("Decryption failed: wrong password or corrupted data")]
127 DecryptionFailed,
128
129 #[error("Not found: {0}")]
131 NotFound(String),
132
133 #[error("Invalid format: {0}")]
135 InvalidFormat(String),
136}
137
138impl Error {
139 pub fn io(source: std::io::Error, path: impl Into<PathBuf>) -> Self {
141 Self::Io {
142 path: Some(path.into()),
143 source,
144 }
145 }
146
147 pub fn io_no_path(source: std::io::Error) -> Self {
149 Self::Io { path: None, source }
150 }
151
152 pub fn column_not_found(name: impl Into<String>) -> Self {
154 Self::ColumnNotFound { name: name.into() }
155 }
156
157 pub fn invalid_config(message: impl Into<String>) -> Self {
159 Self::InvalidConfig {
160 message: message.into(),
161 }
162 }
163
164 pub fn unsupported_format(format: impl Into<String>) -> Self {
166 Self::UnsupportedFormat {
167 format: format.into(),
168 }
169 }
170
171 pub fn schema_mismatch(message: impl Into<String>) -> Self {
173 Self::SchemaMismatch {
174 message: message.into(),
175 }
176 }
177
178 pub fn storage(message: impl Into<String>) -> Self {
180 Self::Storage {
181 message: message.into(),
182 }
183 }
184
185 pub fn transform(message: impl Into<String>) -> Self {
187 Self::Transform {
188 message: message.into(),
189 }
190 }
191
192 pub fn parse(message: impl Into<String>) -> Self {
194 Self::Parse {
195 message: message.into(),
196 }
197 }
198
199 pub fn data(message: impl Into<String>) -> Self {
201 Self::Data {
202 message: message.into(),
203 }
204 }
205
206 #[must_use]
208 pub fn empty_dataset(_name: &str) -> Self {
209 Self::EmptyDataset
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_io_error_with_path() {
219 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
220 let err = Error::io(io_err, "/path/to/file");
221 assert!(err.to_string().contains("/path/to/file"));
222 assert!(err.to_string().contains("file not found"));
223 }
224
225 #[test]
226 fn test_io_error_without_path() {
227 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
228 let err = Error::io_no_path(io_err);
229 assert!(err.to_string().contains("None"));
230 }
231
232 #[test]
233 fn test_index_out_of_bounds() {
234 let err = Error::IndexOutOfBounds { index: 10, len: 5 };
235 assert!(err.to_string().contains("10"));
236 assert!(err.to_string().contains('5'));
237 }
238
239 #[test]
240 fn test_column_not_found() {
241 let err = Error::column_not_found("my_column");
242 assert!(err.to_string().contains("my_column"));
243 }
244
245 #[test]
246 fn test_invalid_config() {
247 let err = Error::invalid_config("batch_size must be positive");
248 assert!(err.to_string().contains("batch_size must be positive"));
249 }
250
251 #[test]
252 fn test_unsupported_format() {
253 let err = Error::unsupported_format("xlsx");
254 assert!(err.to_string().contains("xlsx"));
255 }
256
257 #[test]
258 fn test_schema_mismatch() {
259 let err = Error::schema_mismatch("expected Int64, got Utf8");
260 assert!(err.to_string().contains("expected Int64, got Utf8"));
261 }
262
263 #[test]
264 fn test_storage_error() {
265 let err = Error::storage("connection refused");
266 assert!(err.to_string().contains("connection refused"));
267 }
268
269 #[test]
270 fn test_transform_error() {
271 let err = Error::transform("filter predicate failed");
272 assert!(err.to_string().contains("filter predicate failed"));
273 }
274
275 #[test]
276 fn test_empty_dataset() {
277 let err = Error::EmptyDataset;
278 assert!(err.to_string().contains("empty"));
279 }
280
281 #[test]
282 fn test_empty_dataset_constructor() {
283 let err = Error::empty_dataset("test");
284 assert!(err.to_string().contains("empty"));
285 }
286
287 #[test]
288 fn test_parse_error() {
289 let err = Error::parse("invalid JSON syntax");
290 assert!(err.to_string().contains("invalid JSON syntax"));
291 }
292
293 #[test]
294 fn test_data_error() {
295 let err = Error::data("corrupted record");
296 assert!(err.to_string().contains("corrupted record"));
297 }
298
299 #[test]
300 fn test_format_error() {
301 let err = Error::Format("invalid magic bytes".to_string());
302 assert!(err.to_string().contains("invalid magic bytes"));
303 }
304
305 #[test]
306 fn test_checksum_mismatch() {
307 let err = Error::ChecksumMismatch {
308 expected: 0xDEADBEEF,
309 actual: 0xCAFEBABE,
310 };
311 let msg = err.to_string();
312 assert!(msg.contains("DEADBEEF"));
313 assert!(msg.contains("CAFEBABE"));
314 }
315
316 #[test]
317 fn test_license_expired() {
318 let err = Error::LicenseExpired {
319 expired_at: 1700000000,
320 current_time: 1700100000,
321 };
322 let msg = err.to_string();
323 assert!(msg.contains("expired"));
324 assert!(msg.contains("1700000000"));
325 assert!(msg.contains("1700100000"));
326 }
327
328 #[test]
329 fn test_signature_invalid() {
330 let err = Error::SignatureInvalid;
331 assert!(err.to_string().contains("Signature verification failed"));
332 }
333
334 #[test]
335 fn test_decryption_failed() {
336 let err = Error::DecryptionFailed;
337 assert!(err.to_string().contains("Decryption failed"));
338 }
339}