edgefirst_client/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.
3
4use crate::Progress;
5use tokio::sync::{AcquireError, watch};
6
7/// Comprehensive error type for EdgeFirst Studio Client operations.
8///
9/// This enum covers all possible error conditions that can occur when using
10/// the EdgeFirst Studio Client, from network issues to authentication problems
11/// and data validation errors.
12#[derive(Debug)]
13pub enum Error {
14    /// An I/O error occurred during file operations.
15    IoError(std::io::Error),
16    /// Configuration parsing or loading error.
17    ConfigError(config::ConfigError),
18    /// JSON serialization or deserialization error.
19    JsonError(serde_json::Error),
20    /// HTTP request error from the reqwest client.
21    HttpError(reqwest::Error),
22    /// Maximum number of retries exceeded for an operation.
23    MaxRetriesExceeded(u32),
24    /// URL parsing error.
25    UrlParseError(url::ParseError),
26    /// RPC error with error code and message from the server.
27    RpcError(i32, String),
28    /// Invalid RPC request ID format.
29    InvalidRpcId(String),
30    /// Environment variable error.
31    EnvError(std::env::VarError),
32    /// Semaphore acquisition error for concurrent operations.
33    SemaphoreError(AcquireError),
34    /// Async task join error.
35    JoinError(tokio::task::JoinError),
36    /// Error sending progress updates.
37    ProgressSendError(watch::error::SendError<Progress>),
38    /// Error receiving progress updates.
39    ProgressRecvError(watch::error::RecvError),
40    /// Path prefix stripping error.
41    StripPrefixError(std::path::StripPrefixError),
42    /// Integer parsing error.
43    ParseIntError(std::num::ParseIntError),
44    /// Server returned an invalid or unexpected response.
45    InvalidResponse,
46    /// Requested functionality is not yet implemented.
47    NotImplemented,
48    /// File part size exceeds the maximum allowed limit.
49    PartTooLarge,
50    /// Invalid file type provided.
51    InvalidFileType(String),
52    /// Invalid annotation type provided.
53    InvalidAnnotationType(String),
54    /// Unsupported file format.
55    UnsupportedFormat(String),
56    /// Required image files are missing from the dataset.
57    MissingImages(String),
58    /// Required annotation files are missing from the dataset.
59    MissingAnnotations(String),
60    /// Referenced label is missing or not found.
61    MissingLabel(String),
62    /// Invalid parameters provided to an operation.
63    InvalidParameters(String),
64    /// Attempted to use a feature that is not enabled.
65    FeatureNotEnabled(String),
66    /// Authentication token is empty or not provided.
67    EmptyToken,
68    /// Authentication token format is invalid.
69    InvalidToken,
70    /// Authentication token has expired.
71    TokenExpired,
72    /// User is not authorized to perform the requested operation.
73    Unauthorized,
74    /// Invalid or missing ETag header in HTTP response.
75    InvalidEtag(String),
76    /// Token storage operation error.
77    StorageError(String),
78    /// Polars dataframe operation error (only with "polars" feature).
79    #[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    // Tests for wrapped error types - follow the pattern:
234    // 1. Create inner error
235    // 2. Capture inner error string
236    // 3. Wrap to custom Error type
237    // 4. Capture wrapped error string
238    // 5. Verify inner string is substring of wrapped string
239
240    #[test]
241    fn test_io_error_wrapping() {
242        // 1. Create inner error
243        let inner_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
244        // 2. Capture inner error string
245        let inner_str = inner_err.to_string();
246        // 3. Wrap to custom Error type
247        let wrapped_err: Error = inner_err.into();
248        // 4. Capture wrapped error string
249        let wrapped_str = wrapped_err.to_string();
250        // 5. Verify inner string is substring of wrapped string
251        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        // 1. Create inner error - Force a config error by trying to deserialize empty
263        //    config to a required struct
264        #[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        // 2. Capture inner error string
276        let inner_str = inner_err.to_string();
277        // 3. Wrap to custom Error type
278        let wrapped_err: Error = inner_err.into();
279        // 4. Capture wrapped error string
280        let wrapped_str = wrapped_err.to_string();
281        // 5. Verify inner string is substring of wrapped string
282        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        // 1. Create inner error - invalid JSON
294        let inner_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
295        // 2. Capture inner error string
296        let inner_str = inner_err.to_string();
297        // 3. Wrap to custom Error type
298        let wrapped_err: Error = inner_err.into();
299        // 4. Capture wrapped error string
300        let wrapped_str = wrapped_err.to_string();
301        // 5. Verify inner string is substring of wrapped string
302        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        // 1. Create inner error - invalid URL
314        let inner_err = url::Url::parse("not a valid url").unwrap_err();
315        // 2. Capture inner error string
316        let inner_str = inner_err.to_string();
317        // 3. Wrap to custom Error type
318        let wrapped_err: Error = inner_err.into();
319        // 4. Capture wrapped error string
320        let wrapped_str = wrapped_err.to_string();
321        // 5. Verify inner string is substring of wrapped string
322        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        // 1. Create inner error - missing environment variable
334        let inner_err = std::env::var("NONEXISTENT_VAR_12345").unwrap_err();
335        // 2. Capture inner error string
336        let inner_str = inner_err.to_string();
337        // 3. Wrap to custom Error type
338        let wrapped_err: Error = inner_err.into();
339        // 4. Capture wrapped error string
340        let wrapped_str = wrapped_err.to_string();
341        // 5. Verify inner string is substring of wrapped string
342        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        // 1. Create inner error - strip non-existent prefix
354        let path = Path::new("/foo/bar");
355        let prefix = Path::new("/baz");
356        let inner_err = path.strip_prefix(prefix).unwrap_err();
357        // 2. Capture inner error string
358        let inner_str = inner_err.to_string();
359        // 3. Wrap to custom Error type
360        let wrapped_err: Error = inner_err.into();
361        // 4. Capture wrapped error string
362        let wrapped_str = wrapped_err.to_string();
363        // 5. Verify inner string is substring of wrapped string
364        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        // 1. Create inner error - invalid integer string
376        let inner_err = "not a number".parse::<i32>().unwrap_err();
377        // 2. Capture inner error string
378        let inner_str = inner_err.to_string();
379        // 3. Wrap to custom Error type
380        let wrapped_err: Error = inner_err.into();
381        // 4. Capture wrapped error string
382        let wrapped_str = wrapped_err.to_string();
383        // 5. Verify inner string is substring of wrapped string
384        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        // 1. Create inner error - duplicate column names cause an error
397        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        // 2. Capture inner error string
404        let inner_str = inner_err.to_string();
405        // 3. Wrap to custom Error type
406        let wrapped_err: Error = inner_err.into();
407        // 4. Capture wrapped error string
408        let wrapped_str = wrapped_err.to_string();
409        // 5. Verify inner string is substring of wrapped string
410        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    // Tests for wrapped primitive types - follow the pattern:
420    // 1. Create random primitive value
421    // 2. Capture the primitive as string
422    // 3. Wrap to custom Error type
423    // 4. Capture wrapped error string
424    // 5. Verify primitive string is substring of wrapped string
425
426    #[test]
427    fn test_max_retries_exceeded() {
428        // 1. Create primitive value
429        let retry_count = 42u32;
430        // 2. Capture primitive as string
431        let primitive_str = retry_count.to_string();
432        // 3. Wrap to custom Error type
433        let wrapped_err = Error::MaxRetriesExceeded(retry_count);
434        // 4. Capture wrapped error string
435        let wrapped_str = wrapped_err.to_string();
436        // 5. Verify primitive string is substring of wrapped string
437        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        // 1. Create primitive values
449        let error_code = -32600;
450        let error_msg = "Invalid Request";
451        // 2. Capture primitives as strings
452        let code_str = error_code.to_string();
453        // 3. Wrap to custom Error type
454        let wrapped_err = Error::RpcError(error_code, error_msg.to_string());
455        // 4. Capture wrapped error string
456        let wrapped_str = wrapped_err.to_string();
457        // 5. Verify primitive strings are substrings of wrapped string
458        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        // 1. Create primitive value
476        let invalid_id = "not-a-valid-id-123";
477        // 2. Capture primitive as string (already a string)
478        // 3. Wrap to custom Error type
479        let wrapped_err = Error::InvalidRpcId(invalid_id.to_string());
480        // 4. Capture wrapped error string
481        let wrapped_str = wrapped_err.to_string();
482        // 5. Verify primitive string is substring of wrapped string
483        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        // 1. Create primitive value
495        let file_type = "unknown_format";
496        // 2. Capture primitive as string (already a string)
497        // 3. Wrap to custom Error type
498        let wrapped_err = Error::InvalidFileType(file_type.to_string());
499        // 4. Capture wrapped error string
500        let wrapped_str = wrapped_err.to_string();
501        // 5. Verify primitive string is substring of wrapped string
502        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        // 1. Create primitive value
514        let annotation_type = "unsupported_annotation";
515        // 2. Capture primitive as string (already a string)
516        // 3. Wrap to custom Error type
517        let wrapped_err = Error::InvalidAnnotationType(annotation_type.to_string());
518        // 4. Capture wrapped error string
519        let wrapped_str = wrapped_err.to_string();
520        // 5. Verify primitive string is substring of wrapped string
521        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        // 1. Create primitive value
533        let format = "xyz_format";
534        // 2. Capture primitive as string (already a string)
535        // 3. Wrap to custom Error type
536        let wrapped_err = Error::UnsupportedFormat(format.to_string());
537        // 4. Capture wrapped error string
538        let wrapped_str = wrapped_err.to_string();
539        // 5. Verify primitive string is substring of wrapped string
540        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        // 1. Create primitive value
552        let details = "image001.jpg, image002.jpg";
553        // 2. Capture primitive as string (already a string)
554        // 3. Wrap to custom Error type
555        let wrapped_err = Error::MissingImages(details.to_string());
556        // 4. Capture wrapped error string
557        let wrapped_str = wrapped_err.to_string();
558        // 5. Verify primitive string is substring of wrapped string
559        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        // 1. Create primitive value
571        let details = "annotations.json";
572        // 2. Capture primitive as string (already a string)
573        // 3. Wrap to custom Error type
574        let wrapped_err = Error::MissingAnnotations(details.to_string());
575        // 4. Capture wrapped error string
576        let wrapped_str = wrapped_err.to_string();
577        // 5. Verify primitive string is substring of wrapped string
578        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        // 1. Create primitive value
590        let label = "person";
591        // 2. Capture primitive as string (already a string)
592        // 3. Wrap to custom Error type
593        let wrapped_err = Error::MissingLabel(label.to_string());
594        // 4. Capture wrapped error string
595        let wrapped_str = wrapped_err.to_string();
596        // 5. Verify primitive string is substring of wrapped string
597        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        // 1. Create primitive value
609        let params = "batch_size must be positive";
610        // 2. Capture primitive as string (already a string)
611        // 3. Wrap to custom Error type
612        let wrapped_err = Error::InvalidParameters(params.to_string());
613        // 4. Capture wrapped error string
614        let wrapped_str = wrapped_err.to_string();
615        // 5. Verify primitive string is substring of wrapped string
616        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        // 1. Create primitive value
628        let feature = "polars";
629        // 2. Capture primitive as string (already a string)
630        // 3. Wrap to custom Error type
631        let wrapped_err = Error::FeatureNotEnabled(feature.to_string());
632        // 4. Capture wrapped error string
633        let wrapped_str = wrapped_err.to_string();
634        // 5. Verify primitive string is substring of wrapped string
635        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        // 1. Create primitive value
647        let etag = "malformed-etag-value";
648        // 2. Capture primitive as string (already a string)
649        // 3. Wrap to custom Error type
650        let wrapped_err = Error::InvalidEtag(etag.to_string());
651        // 4. Capture wrapped error string
652        let wrapped_str = wrapped_err.to_string();
653        // 5. Verify primitive string is substring of wrapped string
654        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    // Tests for simple errors without wrapped content
664    // Just verify they can be created and displayed
665
666    #[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}