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    /// Polars dataframe operation error (only with "polars" feature).
77    #[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    // Tests for wrapped error types - follow the pattern:
225    // 1. Create inner error
226    // 2. Capture inner error string
227    // 3. Wrap to custom Error type
228    // 4. Capture wrapped error string
229    // 5. Verify inner string is substring of wrapped string
230
231    #[test]
232    fn test_io_error_wrapping() {
233        // 1. Create inner error
234        let inner_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
235        // 2. Capture inner error string
236        let inner_str = inner_err.to_string();
237        // 3. Wrap to custom Error type
238        let wrapped_err: Error = inner_err.into();
239        // 4. Capture wrapped error string
240        let wrapped_str = wrapped_err.to_string();
241        // 5. Verify inner string is substring of wrapped string
242        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        // 1. Create inner error - Force a config error by trying to deserialize empty
254        //    config to a required struct
255        #[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        // 2. Capture inner error string
267        let inner_str = inner_err.to_string();
268        // 3. Wrap to custom Error type
269        let wrapped_err: Error = inner_err.into();
270        // 4. Capture wrapped error string
271        let wrapped_str = wrapped_err.to_string();
272        // 5. Verify inner string is substring of wrapped string
273        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        // 1. Create inner error - invalid JSON
285        let inner_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
286        // 2. Capture inner error string
287        let inner_str = inner_err.to_string();
288        // 3. Wrap to custom Error type
289        let wrapped_err: Error = inner_err.into();
290        // 4. Capture wrapped error string
291        let wrapped_str = wrapped_err.to_string();
292        // 5. Verify inner string is substring of wrapped string
293        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        // 1. Create inner error - invalid URL
305        let inner_err = url::Url::parse("not a valid url").unwrap_err();
306        // 2. Capture inner error string
307        let inner_str = inner_err.to_string();
308        // 3. Wrap to custom Error type
309        let wrapped_err: Error = inner_err.into();
310        // 4. Capture wrapped error string
311        let wrapped_str = wrapped_err.to_string();
312        // 5. Verify inner string is substring of wrapped string
313        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        // 1. Create inner error - missing environment variable
325        let inner_err = std::env::var("NONEXISTENT_VAR_12345").unwrap_err();
326        // 2. Capture inner error string
327        let inner_str = inner_err.to_string();
328        // 3. Wrap to custom Error type
329        let wrapped_err: Error = inner_err.into();
330        // 4. Capture wrapped error string
331        let wrapped_str = wrapped_err.to_string();
332        // 5. Verify inner string is substring of wrapped string
333        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        // 1. Create inner error - strip non-existent prefix
345        let path = Path::new("/foo/bar");
346        let prefix = Path::new("/baz");
347        let inner_err = path.strip_prefix(prefix).unwrap_err();
348        // 2. Capture inner error string
349        let inner_str = inner_err.to_string();
350        // 3. Wrap to custom Error type
351        let wrapped_err: Error = inner_err.into();
352        // 4. Capture wrapped error string
353        let wrapped_str = wrapped_err.to_string();
354        // 5. Verify inner string is substring of wrapped string
355        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        // 1. Create inner error - invalid integer string
367        let inner_err = "not a number".parse::<i32>().unwrap_err();
368        // 2. Capture inner error string
369        let inner_str = inner_err.to_string();
370        // 3. Wrap to custom Error type
371        let wrapped_err: Error = inner_err.into();
372        // 4. Capture wrapped error string
373        let wrapped_str = wrapped_err.to_string();
374        // 5. Verify inner string is substring of wrapped string
375        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        // 1. Create inner error - duplicate column names cause an error
388        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        // 2. Capture inner error string
395        let inner_str = inner_err.to_string();
396        // 3. Wrap to custom Error type
397        let wrapped_err: Error = inner_err.into();
398        // 4. Capture wrapped error string
399        let wrapped_str = wrapped_err.to_string();
400        // 5. Verify inner string is substring of wrapped string
401        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    // Tests for wrapped primitive types - follow the pattern:
411    // 1. Create random primitive value
412    // 2. Capture the primitive as string
413    // 3. Wrap to custom Error type
414    // 4. Capture wrapped error string
415    // 5. Verify primitive string is substring of wrapped string
416
417    #[test]
418    fn test_max_retries_exceeded() {
419        // 1. Create primitive value
420        let retry_count = 42u32;
421        // 2. Capture primitive as string
422        let primitive_str = retry_count.to_string();
423        // 3. Wrap to custom Error type
424        let wrapped_err = Error::MaxRetriesExceeded(retry_count);
425        // 4. Capture wrapped error string
426        let wrapped_str = wrapped_err.to_string();
427        // 5. Verify primitive string is substring of wrapped string
428        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        // 1. Create primitive values
440        let error_code = -32600;
441        let error_msg = "Invalid Request";
442        // 2. Capture primitives as strings
443        let code_str = error_code.to_string();
444        // 3. Wrap to custom Error type
445        let wrapped_err = Error::RpcError(error_code, error_msg.to_string());
446        // 4. Capture wrapped error string
447        let wrapped_str = wrapped_err.to_string();
448        // 5. Verify primitive strings are substrings of wrapped string
449        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        // 1. Create primitive value
467        let invalid_id = "not-a-valid-id-123";
468        // 2. Capture primitive as string (already a string)
469        // 3. Wrap to custom Error type
470        let wrapped_err = Error::InvalidRpcId(invalid_id.to_string());
471        // 4. Capture wrapped error string
472        let wrapped_str = wrapped_err.to_string();
473        // 5. Verify primitive string is substring of wrapped string
474        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        // 1. Create primitive value
486        let file_type = "unknown_format";
487        // 2. Capture primitive as string (already a string)
488        // 3. Wrap to custom Error type
489        let wrapped_err = Error::InvalidFileType(file_type.to_string());
490        // 4. Capture wrapped error string
491        let wrapped_str = wrapped_err.to_string();
492        // 5. Verify primitive string is substring of wrapped string
493        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        // 1. Create primitive value
505        let annotation_type = "unsupported_annotation";
506        // 2. Capture primitive as string (already a string)
507        // 3. Wrap to custom Error type
508        let wrapped_err = Error::InvalidAnnotationType(annotation_type.to_string());
509        // 4. Capture wrapped error string
510        let wrapped_str = wrapped_err.to_string();
511        // 5. Verify primitive string is substring of wrapped string
512        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        // 1. Create primitive value
524        let format = "xyz_format";
525        // 2. Capture primitive as string (already a string)
526        // 3. Wrap to custom Error type
527        let wrapped_err = Error::UnsupportedFormat(format.to_string());
528        // 4. Capture wrapped error string
529        let wrapped_str = wrapped_err.to_string();
530        // 5. Verify primitive string is substring of wrapped string
531        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        // 1. Create primitive value
543        let details = "image001.jpg, image002.jpg";
544        // 2. Capture primitive as string (already a string)
545        // 3. Wrap to custom Error type
546        let wrapped_err = Error::MissingImages(details.to_string());
547        // 4. Capture wrapped error string
548        let wrapped_str = wrapped_err.to_string();
549        // 5. Verify primitive string is substring of wrapped string
550        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        // 1. Create primitive value
562        let details = "annotations.json";
563        // 2. Capture primitive as string (already a string)
564        // 3. Wrap to custom Error type
565        let wrapped_err = Error::MissingAnnotations(details.to_string());
566        // 4. Capture wrapped error string
567        let wrapped_str = wrapped_err.to_string();
568        // 5. Verify primitive string is substring of wrapped string
569        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        // 1. Create primitive value
581        let label = "person";
582        // 2. Capture primitive as string (already a string)
583        // 3. Wrap to custom Error type
584        let wrapped_err = Error::MissingLabel(label.to_string());
585        // 4. Capture wrapped error string
586        let wrapped_str = wrapped_err.to_string();
587        // 5. Verify primitive string is substring of wrapped string
588        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        // 1. Create primitive value
600        let params = "batch_size must be positive";
601        // 2. Capture primitive as string (already a string)
602        // 3. Wrap to custom Error type
603        let wrapped_err = Error::InvalidParameters(params.to_string());
604        // 4. Capture wrapped error string
605        let wrapped_str = wrapped_err.to_string();
606        // 5. Verify primitive string is substring of wrapped string
607        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        // 1. Create primitive value
619        let feature = "polars";
620        // 2. Capture primitive as string (already a string)
621        // 3. Wrap to custom Error type
622        let wrapped_err = Error::FeatureNotEnabled(feature.to_string());
623        // 4. Capture wrapped error string
624        let wrapped_str = wrapped_err.to_string();
625        // 5. Verify primitive string is substring of wrapped string
626        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        // 1. Create primitive value
638        let etag = "malformed-etag-value";
639        // 2. Capture primitive as string (already a string)
640        // 3. Wrap to custom Error type
641        let wrapped_err = Error::InvalidEtag(etag.to_string());
642        // 4. Capture wrapped error string
643        let wrapped_str = wrapped_err.to_string();
644        // 5. Verify primitive string is substring of wrapped string
645        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    // Tests for simple errors without wrapped content
655    // Just verify they can be created and displayed
656
657    #[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}