1use crate::Progress;
5use tokio::sync::{AcquireError, watch};
6
7#[derive(Debug)]
13pub enum Error {
14 IoError(std::io::Error),
16 ConfigError(config::ConfigError),
18 JsonError(serde_json::Error),
20 HttpError(reqwest::Error),
22 MaxRetriesExceeded(u32),
24 UrlParseError(url::ParseError),
26 RpcError(i32, String),
28 InvalidRpcId(String),
30 EnvError(std::env::VarError),
32 SemaphoreError(AcquireError),
34 JoinError(tokio::task::JoinError),
36 ProgressSendError(watch::error::SendError<Progress>),
38 ProgressRecvError(watch::error::RecvError),
40 StripPrefixError(std::path::StripPrefixError),
42 ParseIntError(std::num::ParseIntError),
44 InvalidResponse,
46 NotImplemented,
48 PartTooLarge,
50 InvalidFileType(String),
52 InvalidAnnotationType(String),
54 UnsupportedFormat(String),
56 MissingImages(String),
58 MissingAnnotations(String),
60 MissingLabel(String),
62 InvalidParameters(String),
64 FeatureNotEnabled(String),
66 EmptyToken,
68 InvalidToken,
70 TokenExpired,
72 Unauthorized,
74 InvalidEtag(String),
76 #[cfg(feature = "polars")]
78 PolarsError(polars::error::PolarsError),
79}
80
81impl From<std::io::Error> for Error {
82 fn from(err: std::io::Error) -> Self {
83 Error::IoError(err)
84 }
85}
86
87impl From<config::ConfigError> for Error {
88 fn from(err: config::ConfigError) -> Self {
89 Error::ConfigError(err)
90 }
91}
92
93impl From<serde_json::Error> for Error {
94 fn from(err: serde_json::Error) -> Self {
95 Error::JsonError(err)
96 }
97}
98
99impl From<reqwest::Error> for Error {
100 fn from(err: reqwest::Error) -> Self {
101 Error::HttpError(err)
102 }
103}
104
105impl From<url::ParseError> for Error {
106 fn from(err: url::ParseError) -> Self {
107 Error::UrlParseError(err)
108 }
109}
110
111impl From<std::env::VarError> for Error {
112 fn from(err: std::env::VarError) -> Self {
113 Error::EnvError(err)
114 }
115}
116
117impl From<AcquireError> for Error {
118 fn from(err: AcquireError) -> Self {
119 Error::SemaphoreError(err)
120 }
121}
122
123impl From<tokio::task::JoinError> for Error {
124 fn from(err: tokio::task::JoinError) -> Self {
125 Error::JoinError(err)
126 }
127}
128
129impl From<watch::error::SendError<Progress>> for Error {
130 fn from(err: watch::error::SendError<Progress>) -> Self {
131 Error::ProgressSendError(err)
132 }
133}
134
135impl From<watch::error::RecvError> for Error {
136 fn from(err: watch::error::RecvError) -> Self {
137 Error::ProgressRecvError(err)
138 }
139}
140
141impl From<std::path::StripPrefixError> for Error {
142 fn from(err: std::path::StripPrefixError) -> Self {
143 Error::StripPrefixError(err)
144 }
145}
146
147impl From<std::num::ParseIntError> for Error {
148 fn from(err: std::num::ParseIntError) -> Self {
149 Error::ParseIntError(err)
150 }
151}
152
153#[cfg(feature = "polars")]
154impl From<polars::error::PolarsError> for Error {
155 fn from(err: polars::error::PolarsError) -> Self {
156 Error::PolarsError(err)
157 }
158}
159
160impl std::fmt::Display for Error {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 Error::IoError(e) => write!(f, "I/O error: {}", e),
164 Error::ConfigError(e) => write!(f, "Configuration error: {}", e),
165 Error::JsonError(e) => write!(f, "JSON error: {}", e),
166 Error::HttpError(e) => write!(f, "HTTP error: {}", e),
167 Error::MaxRetriesExceeded(n) => write!(f, "Maximum retries ({}) exceeded", n),
168 Error::UrlParseError(e) => write!(f, "URL parse error: {}", e),
169 Error::RpcError(code, msg) => write!(f, "RPC error {}: {}", code, msg),
170 Error::InvalidRpcId(id) => write!(f, "Invalid RPC ID: {}", id),
171 Error::EnvError(e) => write!(f, "Environment variable error: {}", e),
172 Error::SemaphoreError(e) => write!(f, "Semaphore error: {}", e),
173 Error::JoinError(e) => write!(f, "Task join error: {}", e),
174 Error::ProgressSendError(e) => write!(f, "Progress send error: {}", e),
175 Error::ProgressRecvError(e) => write!(f, "Progress receive error: {}", e),
176 Error::StripPrefixError(e) => write!(f, "Path prefix error: {}", e),
177 Error::ParseIntError(e) => write!(f, "Integer parse error: {}", e),
178 Error::InvalidResponse => write!(f, "Invalid server response"),
179 Error::NotImplemented => write!(f, "Not implemented"),
180 Error::PartTooLarge => write!(f, "File part size exceeds maximum limit"),
181 Error::InvalidFileType(s) => write!(f, "Invalid file type: {}", s),
182 Error::InvalidAnnotationType(s) => write!(f, "Invalid annotation type: {}", s),
183 Error::UnsupportedFormat(s) => write!(f, "Unsupported format: {}", s),
184 Error::MissingImages(s) => write!(f, "Missing images: {}", s),
185 Error::MissingAnnotations(s) => write!(f, "Missing annotations: {}", s),
186 Error::MissingLabel(s) => write!(f, "Missing label: {}", s),
187 Error::InvalidParameters(s) => write!(f, "Invalid parameters: {}", s),
188 Error::FeatureNotEnabled(s) => write!(f, "Feature not enabled: {}", s),
189 Error::EmptyToken => write!(f, "Authentication token is empty"),
190 Error::InvalidToken => write!(f, "Invalid authentication token"),
191 Error::TokenExpired => write!(f, "Authentication token has expired"),
192 Error::Unauthorized => write!(f, "Unauthorized access"),
193 Error::InvalidEtag(s) => write!(f, "Invalid ETag header: {}", s),
194 #[cfg(feature = "polars")]
195 Error::PolarsError(e) => write!(f, "Polars error: {}", e),
196 }
197 }
198}
199
200impl std::error::Error for Error {
201 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
202 match self {
203 Error::IoError(e) => Some(e),
204 Error::ConfigError(e) => Some(e),
205 Error::JsonError(e) => Some(e),
206 Error::HttpError(e) => Some(e),
207 Error::UrlParseError(e) => Some(e),
208 Error::EnvError(e) => Some(e),
209 Error::JoinError(e) => Some(e),
210 Error::StripPrefixError(e) => Some(e),
211 Error::ParseIntError(e) => Some(e),
212 #[cfg(feature = "polars")]
213 Error::PolarsError(e) => Some(e),
214 _ => None,
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use std::path::Path;
223
224 #[test]
232 fn test_io_error_wrapping() {
233 let inner_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
235 let inner_str = inner_err.to_string();
237 let wrapped_err: Error = inner_err.into();
239 let wrapped_str = wrapped_err.to_string();
241 assert!(
243 wrapped_str.contains(&inner_str),
244 "Wrapped error '{}' should contain inner error '{}'",
245 wrapped_str,
246 inner_str
247 );
248 assert!(wrapped_str.starts_with("I/O error: "));
249 }
250
251 #[test]
252 fn test_config_error_wrapping() {
253 #[derive(Debug, serde::Deserialize)]
256 #[allow(dead_code)]
257 struct RequiredField {
258 required: String,
259 }
260
261 let inner_err = config::Config::builder()
262 .build()
263 .unwrap()
264 .try_deserialize::<RequiredField>()
265 .unwrap_err();
266 let inner_str = inner_err.to_string();
268 let wrapped_err: Error = inner_err.into();
270 let wrapped_str = wrapped_err.to_string();
272 assert!(
274 wrapped_str.contains(&inner_str),
275 "Wrapped error '{}' should contain inner error '{}'",
276 wrapped_str,
277 inner_str
278 );
279 assert!(wrapped_str.starts_with("Configuration error: "));
280 }
281
282 #[test]
283 fn test_json_error_wrapping() {
284 let inner_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
286 let inner_str = inner_err.to_string();
288 let wrapped_err: Error = inner_err.into();
290 let wrapped_str = wrapped_err.to_string();
292 assert!(
294 wrapped_str.contains(&inner_str),
295 "Wrapped error '{}' should contain inner error '{}'",
296 wrapped_str,
297 inner_str
298 );
299 assert!(wrapped_str.starts_with("JSON error: "));
300 }
301
302 #[test]
303 fn test_url_parse_error_wrapping() {
304 let inner_err = url::Url::parse("not a valid url").unwrap_err();
306 let inner_str = inner_err.to_string();
308 let wrapped_err: Error = inner_err.into();
310 let wrapped_str = wrapped_err.to_string();
312 assert!(
314 wrapped_str.contains(&inner_str),
315 "Wrapped error '{}' should contain inner error '{}'",
316 wrapped_str,
317 inner_str
318 );
319 assert!(wrapped_str.starts_with("URL parse error: "));
320 }
321
322 #[test]
323 fn test_env_error_wrapping() {
324 let inner_err = std::env::var("NONEXISTENT_VAR_12345").unwrap_err();
326 let inner_str = inner_err.to_string();
328 let wrapped_err: Error = inner_err.into();
330 let wrapped_str = wrapped_err.to_string();
332 assert!(
334 wrapped_str.contains(&inner_str),
335 "Wrapped error '{}' should contain inner error '{}'",
336 wrapped_str,
337 inner_str
338 );
339 assert!(wrapped_str.starts_with("Environment variable error: "));
340 }
341
342 #[test]
343 fn test_strip_prefix_error_wrapping() {
344 let path = Path::new("/foo/bar");
346 let prefix = Path::new("/baz");
347 let inner_err = path.strip_prefix(prefix).unwrap_err();
348 let inner_str = inner_err.to_string();
350 let wrapped_err: Error = inner_err.into();
352 let wrapped_str = wrapped_err.to_string();
354 assert!(
356 wrapped_str.contains(&inner_str),
357 "Wrapped error '{}' should contain inner error '{}'",
358 wrapped_str,
359 inner_str
360 );
361 assert!(wrapped_str.starts_with("Path prefix error: "));
362 }
363
364 #[test]
365 fn test_parse_int_error_wrapping() {
366 let inner_err = "not a number".parse::<i32>().unwrap_err();
368 let inner_str = inner_err.to_string();
370 let wrapped_err: Error = inner_err.into();
372 let wrapped_str = wrapped_err.to_string();
374 assert!(
376 wrapped_str.contains(&inner_str),
377 "Wrapped error '{}' should contain inner error '{}'",
378 wrapped_str,
379 inner_str
380 );
381 assert!(wrapped_str.starts_with("Integer parse error: "));
382 }
383
384 #[cfg(feature = "polars")]
385 #[test]
386 fn test_polars_error_wrapping() {
387 use polars::prelude::*;
389 let inner_err = DataFrame::new(vec![
390 Series::new("a".into(), &[1, 2, 3]).into(),
391 Series::new("a".into(), &[4, 5, 6]).into(),
392 ])
393 .unwrap_err();
394 let inner_str = inner_err.to_string();
396 let wrapped_err: Error = inner_err.into();
398 let wrapped_str = wrapped_err.to_string();
400 assert!(
402 wrapped_str.contains(&inner_str),
403 "Wrapped error '{}' should contain inner error '{}'",
404 wrapped_str,
405 inner_str
406 );
407 assert!(wrapped_str.starts_with("Polars error: "));
408 }
409
410 #[test]
418 fn test_max_retries_exceeded() {
419 let retry_count = 42u32;
421 let primitive_str = retry_count.to_string();
423 let wrapped_err = Error::MaxRetriesExceeded(retry_count);
425 let wrapped_str = wrapped_err.to_string();
427 assert!(
429 wrapped_str.contains(&primitive_str),
430 "Wrapped error '{}' should contain retry count '{}'",
431 wrapped_str,
432 primitive_str
433 );
434 assert!(wrapped_str.starts_with("Maximum retries"));
435 }
436
437 #[test]
438 fn test_rpc_error() {
439 let error_code = -32600;
441 let error_msg = "Invalid Request";
442 let code_str = error_code.to_string();
444 let wrapped_err = Error::RpcError(error_code, error_msg.to_string());
446 let wrapped_str = wrapped_err.to_string();
448 assert!(
450 wrapped_str.contains(&code_str),
451 "Wrapped error '{}' should contain error code '{}'",
452 wrapped_str,
453 code_str
454 );
455 assert!(
456 wrapped_str.contains(error_msg),
457 "Wrapped error '{}' should contain error message '{}'",
458 wrapped_str,
459 error_msg
460 );
461 assert!(wrapped_str.starts_with("RPC error"));
462 }
463
464 #[test]
465 fn test_invalid_rpc_id() {
466 let invalid_id = "not-a-valid-id-123";
468 let wrapped_err = Error::InvalidRpcId(invalid_id.to_string());
471 let wrapped_str = wrapped_err.to_string();
473 assert!(
475 wrapped_str.contains(invalid_id),
476 "Wrapped error '{}' should contain invalid ID '{}'",
477 wrapped_str,
478 invalid_id
479 );
480 assert!(wrapped_str.starts_with("Invalid RPC ID: "));
481 }
482
483 #[test]
484 fn test_invalid_file_type() {
485 let file_type = "unknown_format";
487 let wrapped_err = Error::InvalidFileType(file_type.to_string());
490 let wrapped_str = wrapped_err.to_string();
492 assert!(
494 wrapped_str.contains(file_type),
495 "Wrapped error '{}' should contain file type '{}'",
496 wrapped_str,
497 file_type
498 );
499 assert!(wrapped_str.starts_with("Invalid file type: "));
500 }
501
502 #[test]
503 fn test_invalid_annotation_type() {
504 let annotation_type = "unsupported_annotation";
506 let wrapped_err = Error::InvalidAnnotationType(annotation_type.to_string());
509 let wrapped_str = wrapped_err.to_string();
511 assert!(
513 wrapped_str.contains(annotation_type),
514 "Wrapped error '{}' should contain annotation type '{}'",
515 wrapped_str,
516 annotation_type
517 );
518 assert!(wrapped_str.starts_with("Invalid annotation type: "));
519 }
520
521 #[test]
522 fn test_unsupported_format() {
523 let format = "xyz_format";
525 let wrapped_err = Error::UnsupportedFormat(format.to_string());
528 let wrapped_str = wrapped_err.to_string();
530 assert!(
532 wrapped_str.contains(format),
533 "Wrapped error '{}' should contain format '{}'",
534 wrapped_str,
535 format
536 );
537 assert!(wrapped_str.starts_with("Unsupported format: "));
538 }
539
540 #[test]
541 fn test_missing_images() {
542 let details = "image001.jpg, image002.jpg";
544 let wrapped_err = Error::MissingImages(details.to_string());
547 let wrapped_str = wrapped_err.to_string();
549 assert!(
551 wrapped_str.contains(details),
552 "Wrapped error '{}' should contain details '{}'",
553 wrapped_str,
554 details
555 );
556 assert!(wrapped_str.starts_with("Missing images: "));
557 }
558
559 #[test]
560 fn test_missing_annotations() {
561 let details = "annotations.json";
563 let wrapped_err = Error::MissingAnnotations(details.to_string());
566 let wrapped_str = wrapped_err.to_string();
568 assert!(
570 wrapped_str.contains(details),
571 "Wrapped error '{}' should contain details '{}'",
572 wrapped_str,
573 details
574 );
575 assert!(wrapped_str.starts_with("Missing annotations: "));
576 }
577
578 #[test]
579 fn test_missing_label() {
580 let label = "person";
582 let wrapped_err = Error::MissingLabel(label.to_string());
585 let wrapped_str = wrapped_err.to_string();
587 assert!(
589 wrapped_str.contains(label),
590 "Wrapped error '{}' should contain label '{}'",
591 wrapped_str,
592 label
593 );
594 assert!(wrapped_str.starts_with("Missing label: "));
595 }
596
597 #[test]
598 fn test_invalid_parameters() {
599 let params = "batch_size must be positive";
601 let wrapped_err = Error::InvalidParameters(params.to_string());
604 let wrapped_str = wrapped_err.to_string();
606 assert!(
608 wrapped_str.contains(params),
609 "Wrapped error '{}' should contain params '{}'",
610 wrapped_str,
611 params
612 );
613 assert!(wrapped_str.starts_with("Invalid parameters: "));
614 }
615
616 #[test]
617 fn test_feature_not_enabled() {
618 let feature = "polars";
620 let wrapped_err = Error::FeatureNotEnabled(feature.to_string());
623 let wrapped_str = wrapped_err.to_string();
625 assert!(
627 wrapped_str.contains(feature),
628 "Wrapped error '{}' should contain feature '{}'",
629 wrapped_str,
630 feature
631 );
632 assert!(wrapped_str.starts_with("Feature not enabled: "));
633 }
634
635 #[test]
636 fn test_invalid_etag() {
637 let etag = "malformed-etag-value";
639 let wrapped_err = Error::InvalidEtag(etag.to_string());
642 let wrapped_str = wrapped_err.to_string();
644 assert!(
646 wrapped_str.contains(etag),
647 "Wrapped error '{}' should contain etag '{}'",
648 wrapped_str,
649 etag
650 );
651 assert!(wrapped_str.starts_with("Invalid ETag header: "));
652 }
653
654 #[test]
658 fn test_invalid_response() {
659 let err = Error::InvalidResponse;
660 let err_str = err.to_string();
661 assert_eq!(err_str, "Invalid server response");
662 }
663
664 #[test]
665 fn test_not_implemented() {
666 let err = Error::NotImplemented;
667 let err_str = err.to_string();
668 assert_eq!(err_str, "Not implemented");
669 }
670
671 #[test]
672 fn test_part_too_large() {
673 let err = Error::PartTooLarge;
674 let err_str = err.to_string();
675 assert_eq!(err_str, "File part size exceeds maximum limit");
676 }
677
678 #[test]
679 fn test_empty_token() {
680 let err = Error::EmptyToken;
681 let err_str = err.to_string();
682 assert_eq!(err_str, "Authentication token is empty");
683 }
684
685 #[test]
686 fn test_invalid_token() {
687 let err = Error::InvalidToken;
688 let err_str = err.to_string();
689 assert_eq!(err_str, "Invalid authentication token");
690 }
691
692 #[test]
693 fn test_token_expired() {
694 let err = Error::TokenExpired;
695 let err_str = err.to_string();
696 assert_eq!(err_str, "Authentication token has expired");
697 }
698
699 #[test]
700 fn test_unauthorized() {
701 let err = Error::Unauthorized;
702 let err_str = err.to_string();
703 assert_eq!(err_str, "Unauthorized access");
704 }
705}