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 StorageError(String),
78 #[cfg(feature = "polars")]
80 PolarsError(polars::error::PolarsError),
81}
82
83impl From<std::io::Error> for Error {
84 fn from(err: std::io::Error) -> Self {
85 Error::IoError(err)
86 }
87}
88
89impl From<config::ConfigError> for Error {
90 fn from(err: config::ConfigError) -> Self {
91 Error::ConfigError(err)
92 }
93}
94
95impl From<serde_json::Error> for Error {
96 fn from(err: serde_json::Error) -> Self {
97 Error::JsonError(err)
98 }
99}
100
101impl From<reqwest::Error> for Error {
102 fn from(err: reqwest::Error) -> Self {
103 Error::HttpError(err)
104 }
105}
106
107impl From<url::ParseError> for Error {
108 fn from(err: url::ParseError) -> Self {
109 Error::UrlParseError(err)
110 }
111}
112
113impl From<std::env::VarError> for Error {
114 fn from(err: std::env::VarError) -> Self {
115 Error::EnvError(err)
116 }
117}
118
119impl From<AcquireError> for Error {
120 fn from(err: AcquireError) -> Self {
121 Error::SemaphoreError(err)
122 }
123}
124
125impl From<tokio::task::JoinError> for Error {
126 fn from(err: tokio::task::JoinError) -> Self {
127 Error::JoinError(err)
128 }
129}
130
131impl From<watch::error::SendError<Progress>> for Error {
132 fn from(err: watch::error::SendError<Progress>) -> Self {
133 Error::ProgressSendError(err)
134 }
135}
136
137impl From<watch::error::RecvError> for Error {
138 fn from(err: watch::error::RecvError) -> Self {
139 Error::ProgressRecvError(err)
140 }
141}
142
143impl From<std::path::StripPrefixError> for Error {
144 fn from(err: std::path::StripPrefixError) -> Self {
145 Error::StripPrefixError(err)
146 }
147}
148
149impl From<std::num::ParseIntError> for Error {
150 fn from(err: std::num::ParseIntError) -> Self {
151 Error::ParseIntError(err)
152 }
153}
154
155impl From<crate::storage::StorageError> for Error {
156 fn from(err: crate::storage::StorageError) -> Self {
157 Error::StorageError(err.to_string())
158 }
159}
160
161#[cfg(feature = "polars")]
162impl From<polars::error::PolarsError> for Error {
163 fn from(err: polars::error::PolarsError) -> Self {
164 Error::PolarsError(err)
165 }
166}
167
168impl std::fmt::Display for Error {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 match self {
171 Error::IoError(e) => write!(f, "I/O error: {}", e),
172 Error::ConfigError(e) => write!(f, "Configuration error: {}", e),
173 Error::JsonError(e) => write!(f, "JSON error: {}", e),
174 Error::HttpError(e) => write!(f, "HTTP error: {}", e),
175 Error::MaxRetriesExceeded(n) => write!(f, "Maximum retries ({}) exceeded", n),
176 Error::UrlParseError(e) => write!(f, "URL parse error: {}", e),
177 Error::RpcError(code, msg) => write!(f, "RPC error {}: {}", code, msg),
178 Error::InvalidRpcId(id) => write!(f, "Invalid RPC ID: {}", id),
179 Error::EnvError(e) => write!(f, "Environment variable error: {}", e),
180 Error::SemaphoreError(e) => write!(f, "Semaphore error: {}", e),
181 Error::JoinError(e) => write!(f, "Task join error: {}", e),
182 Error::ProgressSendError(e) => write!(f, "Progress send error: {}", e),
183 Error::ProgressRecvError(e) => write!(f, "Progress receive error: {}", e),
184 Error::StripPrefixError(e) => write!(f, "Path prefix error: {}", e),
185 Error::ParseIntError(e) => write!(f, "Integer parse error: {}", e),
186 Error::InvalidResponse => write!(f, "Invalid server response"),
187 Error::NotImplemented => write!(f, "Not implemented"),
188 Error::PartTooLarge => write!(f, "File part size exceeds maximum limit"),
189 Error::InvalidFileType(s) => write!(f, "Invalid file type: {}", s),
190 Error::InvalidAnnotationType(s) => write!(f, "Invalid annotation type: {}", s),
191 Error::UnsupportedFormat(s) => write!(f, "Unsupported format: {}", s),
192 Error::MissingImages(s) => write!(f, "Missing images: {}", s),
193 Error::MissingAnnotations(s) => write!(f, "Missing annotations: {}", s),
194 Error::MissingLabel(s) => write!(f, "Missing label: {}", s),
195 Error::InvalidParameters(s) => write!(f, "Invalid parameters: {}", s),
196 Error::FeatureNotEnabled(s) => write!(f, "Feature not enabled: {}", s),
197 Error::EmptyToken => write!(f, "Authentication token is empty"),
198 Error::InvalidToken => write!(f, "Invalid authentication token"),
199 Error::TokenExpired => write!(f, "Authentication token has expired"),
200 Error::Unauthorized => write!(f, "Unauthorized access"),
201 Error::InvalidEtag(s) => write!(f, "Invalid ETag header: {}", s),
202 Error::StorageError(s) => write!(f, "Token storage error: {}", s),
203 #[cfg(feature = "polars")]
204 Error::PolarsError(e) => write!(f, "Polars error: {}", e),
205 }
206 }
207}
208
209impl std::error::Error for Error {
210 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
211 match self {
212 Error::IoError(e) => Some(e),
213 Error::ConfigError(e) => Some(e),
214 Error::JsonError(e) => Some(e),
215 Error::HttpError(e) => Some(e),
216 Error::UrlParseError(e) => Some(e),
217 Error::EnvError(e) => Some(e),
218 Error::JoinError(e) => Some(e),
219 Error::StripPrefixError(e) => Some(e),
220 Error::ParseIntError(e) => Some(e),
221 #[cfg(feature = "polars")]
222 Error::PolarsError(e) => Some(e),
223 _ => None,
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use std::path::Path;
232
233 #[test]
241 fn test_io_error_wrapping() {
242 let inner_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
244 let inner_str = inner_err.to_string();
246 let wrapped_err: Error = inner_err.into();
248 let wrapped_str = wrapped_err.to_string();
250 assert!(
252 wrapped_str.contains(&inner_str),
253 "Wrapped error '{}' should contain inner error '{}'",
254 wrapped_str,
255 inner_str
256 );
257 assert!(wrapped_str.starts_with("I/O error: "));
258 }
259
260 #[test]
261 fn test_config_error_wrapping() {
262 #[derive(Debug, serde::Deserialize)]
265 #[allow(dead_code)]
266 struct RequiredField {
267 required: String,
268 }
269
270 let inner_err = config::Config::builder()
271 .build()
272 .unwrap()
273 .try_deserialize::<RequiredField>()
274 .unwrap_err();
275 let inner_str = inner_err.to_string();
277 let wrapped_err: Error = inner_err.into();
279 let wrapped_str = wrapped_err.to_string();
281 assert!(
283 wrapped_str.contains(&inner_str),
284 "Wrapped error '{}' should contain inner error '{}'",
285 wrapped_str,
286 inner_str
287 );
288 assert!(wrapped_str.starts_with("Configuration error: "));
289 }
290
291 #[test]
292 fn test_json_error_wrapping() {
293 let inner_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
295 let inner_str = inner_err.to_string();
297 let wrapped_err: Error = inner_err.into();
299 let wrapped_str = wrapped_err.to_string();
301 assert!(
303 wrapped_str.contains(&inner_str),
304 "Wrapped error '{}' should contain inner error '{}'",
305 wrapped_str,
306 inner_str
307 );
308 assert!(wrapped_str.starts_with("JSON error: "));
309 }
310
311 #[test]
312 fn test_url_parse_error_wrapping() {
313 let inner_err = url::Url::parse("not a valid url").unwrap_err();
315 let inner_str = inner_err.to_string();
317 let wrapped_err: Error = inner_err.into();
319 let wrapped_str = wrapped_err.to_string();
321 assert!(
323 wrapped_str.contains(&inner_str),
324 "Wrapped error '{}' should contain inner error '{}'",
325 wrapped_str,
326 inner_str
327 );
328 assert!(wrapped_str.starts_with("URL parse error: "));
329 }
330
331 #[test]
332 fn test_env_error_wrapping() {
333 let inner_err = std::env::var("NONEXISTENT_VAR_12345").unwrap_err();
335 let inner_str = inner_err.to_string();
337 let wrapped_err: Error = inner_err.into();
339 let wrapped_str = wrapped_err.to_string();
341 assert!(
343 wrapped_str.contains(&inner_str),
344 "Wrapped error '{}' should contain inner error '{}'",
345 wrapped_str,
346 inner_str
347 );
348 assert!(wrapped_str.starts_with("Environment variable error: "));
349 }
350
351 #[test]
352 fn test_strip_prefix_error_wrapping() {
353 let path = Path::new("/foo/bar");
355 let prefix = Path::new("/baz");
356 let inner_err = path.strip_prefix(prefix).unwrap_err();
357 let inner_str = inner_err.to_string();
359 let wrapped_err: Error = inner_err.into();
361 let wrapped_str = wrapped_err.to_string();
363 assert!(
365 wrapped_str.contains(&inner_str),
366 "Wrapped error '{}' should contain inner error '{}'",
367 wrapped_str,
368 inner_str
369 );
370 assert!(wrapped_str.starts_with("Path prefix error: "));
371 }
372
373 #[test]
374 fn test_parse_int_error_wrapping() {
375 let inner_err = "not a number".parse::<i32>().unwrap_err();
377 let inner_str = inner_err.to_string();
379 let wrapped_err: Error = inner_err.into();
381 let wrapped_str = wrapped_err.to_string();
383 assert!(
385 wrapped_str.contains(&inner_str),
386 "Wrapped error '{}' should contain inner error '{}'",
387 wrapped_str,
388 inner_str
389 );
390 assert!(wrapped_str.starts_with("Integer parse error: "));
391 }
392
393 #[cfg(feature = "polars")]
394 #[test]
395 fn test_polars_error_wrapping() {
396 use polars::prelude::*;
398 let inner_err = DataFrame::new(vec![
399 Series::new("a".into(), &[1, 2, 3]).into(),
400 Series::new("a".into(), &[4, 5, 6]).into(),
401 ])
402 .unwrap_err();
403 let inner_str = inner_err.to_string();
405 let wrapped_err: Error = inner_err.into();
407 let wrapped_str = wrapped_err.to_string();
409 assert!(
411 wrapped_str.contains(&inner_str),
412 "Wrapped error '{}' should contain inner error '{}'",
413 wrapped_str,
414 inner_str
415 );
416 assert!(wrapped_str.starts_with("Polars error: "));
417 }
418
419 #[test]
427 fn test_max_retries_exceeded() {
428 let retry_count = 42u32;
430 let primitive_str = retry_count.to_string();
432 let wrapped_err = Error::MaxRetriesExceeded(retry_count);
434 let wrapped_str = wrapped_err.to_string();
436 assert!(
438 wrapped_str.contains(&primitive_str),
439 "Wrapped error '{}' should contain retry count '{}'",
440 wrapped_str,
441 primitive_str
442 );
443 assert!(wrapped_str.starts_with("Maximum retries"));
444 }
445
446 #[test]
447 fn test_rpc_error() {
448 let error_code = -32600;
450 let error_msg = "Invalid Request";
451 let code_str = error_code.to_string();
453 let wrapped_err = Error::RpcError(error_code, error_msg.to_string());
455 let wrapped_str = wrapped_err.to_string();
457 assert!(
459 wrapped_str.contains(&code_str),
460 "Wrapped error '{}' should contain error code '{}'",
461 wrapped_str,
462 code_str
463 );
464 assert!(
465 wrapped_str.contains(error_msg),
466 "Wrapped error '{}' should contain error message '{}'",
467 wrapped_str,
468 error_msg
469 );
470 assert!(wrapped_str.starts_with("RPC error"));
471 }
472
473 #[test]
474 fn test_invalid_rpc_id() {
475 let invalid_id = "not-a-valid-id-123";
477 let wrapped_err = Error::InvalidRpcId(invalid_id.to_string());
480 let wrapped_str = wrapped_err.to_string();
482 assert!(
484 wrapped_str.contains(invalid_id),
485 "Wrapped error '{}' should contain invalid ID '{}'",
486 wrapped_str,
487 invalid_id
488 );
489 assert!(wrapped_str.starts_with("Invalid RPC ID: "));
490 }
491
492 #[test]
493 fn test_invalid_file_type() {
494 let file_type = "unknown_format";
496 let wrapped_err = Error::InvalidFileType(file_type.to_string());
499 let wrapped_str = wrapped_err.to_string();
501 assert!(
503 wrapped_str.contains(file_type),
504 "Wrapped error '{}' should contain file type '{}'",
505 wrapped_str,
506 file_type
507 );
508 assert!(wrapped_str.starts_with("Invalid file type: "));
509 }
510
511 #[test]
512 fn test_invalid_annotation_type() {
513 let annotation_type = "unsupported_annotation";
515 let wrapped_err = Error::InvalidAnnotationType(annotation_type.to_string());
518 let wrapped_str = wrapped_err.to_string();
520 assert!(
522 wrapped_str.contains(annotation_type),
523 "Wrapped error '{}' should contain annotation type '{}'",
524 wrapped_str,
525 annotation_type
526 );
527 assert!(wrapped_str.starts_with("Invalid annotation type: "));
528 }
529
530 #[test]
531 fn test_unsupported_format() {
532 let format = "xyz_format";
534 let wrapped_err = Error::UnsupportedFormat(format.to_string());
537 let wrapped_str = wrapped_err.to_string();
539 assert!(
541 wrapped_str.contains(format),
542 "Wrapped error '{}' should contain format '{}'",
543 wrapped_str,
544 format
545 );
546 assert!(wrapped_str.starts_with("Unsupported format: "));
547 }
548
549 #[test]
550 fn test_missing_images() {
551 let details = "image001.jpg, image002.jpg";
553 let wrapped_err = Error::MissingImages(details.to_string());
556 let wrapped_str = wrapped_err.to_string();
558 assert!(
560 wrapped_str.contains(details),
561 "Wrapped error '{}' should contain details '{}'",
562 wrapped_str,
563 details
564 );
565 assert!(wrapped_str.starts_with("Missing images: "));
566 }
567
568 #[test]
569 fn test_missing_annotations() {
570 let details = "annotations.json";
572 let wrapped_err = Error::MissingAnnotations(details.to_string());
575 let wrapped_str = wrapped_err.to_string();
577 assert!(
579 wrapped_str.contains(details),
580 "Wrapped error '{}' should contain details '{}'",
581 wrapped_str,
582 details
583 );
584 assert!(wrapped_str.starts_with("Missing annotations: "));
585 }
586
587 #[test]
588 fn test_missing_label() {
589 let label = "person";
591 let wrapped_err = Error::MissingLabel(label.to_string());
594 let wrapped_str = wrapped_err.to_string();
596 assert!(
598 wrapped_str.contains(label),
599 "Wrapped error '{}' should contain label '{}'",
600 wrapped_str,
601 label
602 );
603 assert!(wrapped_str.starts_with("Missing label: "));
604 }
605
606 #[test]
607 fn test_invalid_parameters() {
608 let params = "batch_size must be positive";
610 let wrapped_err = Error::InvalidParameters(params.to_string());
613 let wrapped_str = wrapped_err.to_string();
615 assert!(
617 wrapped_str.contains(params),
618 "Wrapped error '{}' should contain params '{}'",
619 wrapped_str,
620 params
621 );
622 assert!(wrapped_str.starts_with("Invalid parameters: "));
623 }
624
625 #[test]
626 fn test_feature_not_enabled() {
627 let feature = "polars";
629 let wrapped_err = Error::FeatureNotEnabled(feature.to_string());
632 let wrapped_str = wrapped_err.to_string();
634 assert!(
636 wrapped_str.contains(feature),
637 "Wrapped error '{}' should contain feature '{}'",
638 wrapped_str,
639 feature
640 );
641 assert!(wrapped_str.starts_with("Feature not enabled: "));
642 }
643
644 #[test]
645 fn test_invalid_etag() {
646 let etag = "malformed-etag-value";
648 let wrapped_err = Error::InvalidEtag(etag.to_string());
651 let wrapped_str = wrapped_err.to_string();
653 assert!(
655 wrapped_str.contains(etag),
656 "Wrapped error '{}' should contain etag '{}'",
657 wrapped_str,
658 etag
659 );
660 assert!(wrapped_str.starts_with("Invalid ETag header: "));
661 }
662
663 #[test]
667 fn test_invalid_response() {
668 let err = Error::InvalidResponse;
669 let err_str = err.to_string();
670 assert_eq!(err_str, "Invalid server response");
671 }
672
673 #[test]
674 fn test_not_implemented() {
675 let err = Error::NotImplemented;
676 let err_str = err.to_string();
677 assert_eq!(err_str, "Not implemented");
678 }
679
680 #[test]
681 fn test_part_too_large() {
682 let err = Error::PartTooLarge;
683 let err_str = err.to_string();
684 assert_eq!(err_str, "File part size exceeds maximum limit");
685 }
686
687 #[test]
688 fn test_empty_token() {
689 let err = Error::EmptyToken;
690 let err_str = err.to_string();
691 assert_eq!(err_str, "Authentication token is empty");
692 }
693
694 #[test]
695 fn test_invalid_token() {
696 let err = Error::InvalidToken;
697 let err_str = err.to_string();
698 assert_eq!(err_str, "Invalid authentication token");
699 }
700
701 #[test]
702 fn test_token_expired() {
703 let err = Error::TokenExpired;
704 let err_str = err.to_string();
705 assert_eq!(err_str, "Authentication token has expired");
706 }
707
708 #[test]
709 fn test_unauthorized() {
710 let err = Error::Unauthorized;
711 let err_str = err.to_string();
712 assert_eq!(err_str, "Unauthorized access");
713 }
714}