rustfs_filemeta/
error.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub type Result<T> = core::result::Result<T, Error>;
16
17#[derive(thiserror::Error, Debug)]
18pub enum Error {
19    #[error("File not found")]
20    FileNotFound,
21    #[error("File version not found")]
22    FileVersionNotFound,
23
24    #[error("Volume not found")]
25    VolumeNotFound,
26
27    #[error("File corrupt")]
28    FileCorrupt,
29
30    #[error("Done for now")]
31    DoneForNow,
32
33    #[error("Method not allowed")]
34    MethodNotAllowed,
35
36    #[error("Unexpected error")]
37    Unexpected,
38
39    #[error("I/O error: {0}")]
40    Io(std::io::Error),
41
42    #[error("rmp serde decode error: {0}")]
43    RmpSerdeDecode(String),
44
45    #[error("rmp serde encode error: {0}")]
46    RmpSerdeEncode(String),
47
48    #[error("Invalid UTF-8: {0}")]
49    FromUtf8(String),
50
51    #[error("rmp decode value read error: {0}")]
52    RmpDecodeValueRead(String),
53
54    #[error("rmp encode value write error: {0}")]
55    RmpEncodeValueWrite(String),
56
57    #[error("rmp decode num value read error: {0}")]
58    RmpDecodeNumValueRead(String),
59
60    #[error("rmp decode marker read error: {0}")]
61    RmpDecodeMarkerRead(String),
62
63    #[error("time component range error: {0}")]
64    TimeComponentRange(String),
65
66    #[error("uuid parse error: {0}")]
67    UuidParse(String),
68}
69
70impl Error {
71    pub fn other<E>(error: E) -> Error
72    where
73        E: Into<Box<dyn std::error::Error + Send + Sync>>,
74    {
75        std::io::Error::other(error).into()
76    }
77}
78
79impl PartialEq for Error {
80    fn eq(&self, other: &Self) -> bool {
81        match (self, other) {
82            (Error::FileCorrupt, Error::FileCorrupt) => true,
83            (Error::DoneForNow, Error::DoneForNow) => true,
84            (Error::MethodNotAllowed, Error::MethodNotAllowed) => true,
85            (Error::FileNotFound, Error::FileNotFound) => true,
86            (Error::FileVersionNotFound, Error::FileVersionNotFound) => true,
87            (Error::VolumeNotFound, Error::VolumeNotFound) => true,
88            (Error::Io(e1), Error::Io(e2)) => e1.kind() == e2.kind() && e1.to_string() == e2.to_string(),
89            (Error::RmpSerdeDecode(e1), Error::RmpSerdeDecode(e2)) => e1 == e2,
90            (Error::RmpSerdeEncode(e1), Error::RmpSerdeEncode(e2)) => e1 == e2,
91            (Error::RmpDecodeValueRead(e1), Error::RmpDecodeValueRead(e2)) => e1 == e2,
92            (Error::RmpEncodeValueWrite(e1), Error::RmpEncodeValueWrite(e2)) => e1 == e2,
93            (Error::RmpDecodeNumValueRead(e1), Error::RmpDecodeNumValueRead(e2)) => e1 == e2,
94            (Error::TimeComponentRange(e1), Error::TimeComponentRange(e2)) => e1 == e2,
95            (Error::UuidParse(e1), Error::UuidParse(e2)) => e1 == e2,
96            (Error::Unexpected, Error::Unexpected) => true,
97            (a, b) => a.to_string() == b.to_string(),
98        }
99    }
100}
101
102impl Clone for Error {
103    fn clone(&self) -> Self {
104        match self {
105            Error::FileNotFound => Error::FileNotFound,
106            Error::FileVersionNotFound => Error::FileVersionNotFound,
107            Error::FileCorrupt => Error::FileCorrupt,
108            Error::DoneForNow => Error::DoneForNow,
109            Error::MethodNotAllowed => Error::MethodNotAllowed,
110            Error::VolumeNotFound => Error::VolumeNotFound,
111            Error::Io(e) => Error::Io(std::io::Error::new(e.kind(), e.to_string())),
112            Error::RmpSerdeDecode(s) => Error::RmpSerdeDecode(s.clone()),
113            Error::RmpSerdeEncode(s) => Error::RmpSerdeEncode(s.clone()),
114            Error::FromUtf8(s) => Error::FromUtf8(s.clone()),
115            Error::RmpDecodeValueRead(s) => Error::RmpDecodeValueRead(s.clone()),
116            Error::RmpEncodeValueWrite(s) => Error::RmpEncodeValueWrite(s.clone()),
117            Error::RmpDecodeNumValueRead(s) => Error::RmpDecodeNumValueRead(s.clone()),
118            Error::RmpDecodeMarkerRead(s) => Error::RmpDecodeMarkerRead(s.clone()),
119            Error::TimeComponentRange(s) => Error::TimeComponentRange(s.clone()),
120            Error::UuidParse(s) => Error::UuidParse(s.clone()),
121            Error::Unexpected => Error::Unexpected,
122        }
123    }
124}
125
126impl From<std::io::Error> for Error {
127    fn from(e: std::io::Error) -> Self {
128        match e.kind() {
129            std::io::ErrorKind::UnexpectedEof => Error::Unexpected,
130            _ => Error::Io(e),
131        }
132    }
133}
134
135impl From<Error> for std::io::Error {
136    fn from(e: Error) -> Self {
137        match e {
138            Error::Unexpected => std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Unexpected EOF"),
139            Error::Io(e) => e,
140            _ => std::io::Error::other(e.to_string()),
141        }
142    }
143}
144
145impl From<rmp_serde::decode::Error> for Error {
146    fn from(e: rmp_serde::decode::Error) -> Self {
147        Error::RmpSerdeDecode(e.to_string())
148    }
149}
150
151impl From<rmp_serde::encode::Error> for Error {
152    fn from(e: rmp_serde::encode::Error) -> Self {
153        Error::RmpSerdeEncode(e.to_string())
154    }
155}
156
157impl From<std::string::FromUtf8Error> for Error {
158    fn from(e: std::string::FromUtf8Error) -> Self {
159        Error::FromUtf8(e.to_string())
160    }
161}
162
163impl From<rmp::decode::ValueReadError> for Error {
164    fn from(e: rmp::decode::ValueReadError) -> Self {
165        Error::RmpDecodeValueRead(e.to_string())
166    }
167}
168
169impl From<rmp::encode::ValueWriteError> for Error {
170    fn from(e: rmp::encode::ValueWriteError) -> Self {
171        Error::RmpEncodeValueWrite(e.to_string())
172    }
173}
174
175impl From<rmp::decode::NumValueReadError> for Error {
176    fn from(e: rmp::decode::NumValueReadError) -> Self {
177        Error::RmpDecodeNumValueRead(e.to_string())
178    }
179}
180
181impl From<time::error::ComponentRange> for Error {
182    fn from(e: time::error::ComponentRange) -> Self {
183        Error::TimeComponentRange(e.to_string())
184    }
185}
186
187impl From<uuid::Error> for Error {
188    fn from(e: uuid::Error) -> Self {
189        Error::UuidParse(e.to_string())
190    }
191}
192
193impl From<rmp::decode::MarkerReadError> for Error {
194    fn from(e: rmp::decode::MarkerReadError) -> Self {
195        let serr = format!("{e:?}");
196        Error::RmpDecodeMarkerRead(serr)
197    }
198}
199
200pub fn is_io_eof(e: &Error) -> bool {
201    match e {
202        Error::Io(e) => e.kind() == std::io::ErrorKind::UnexpectedEof,
203        _ => false,
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::io::{Error as IoError, ErrorKind};
211
212    #[test]
213    fn test_filemeta_error_from_io_error() {
214        let io_error = IoError::new(ErrorKind::PermissionDenied, "permission denied");
215        let filemeta_error: Error = io_error.into();
216
217        match filemeta_error {
218            Error::Io(inner_io) => {
219                assert_eq!(inner_io.kind(), ErrorKind::PermissionDenied);
220                assert!(inner_io.to_string().contains("permission denied"));
221            }
222            _ => panic!("Expected Io variant"),
223        }
224    }
225
226    #[test]
227    fn test_filemeta_error_other_function() {
228        let custom_error = "Custom filemeta error";
229        let filemeta_error = Error::other(custom_error);
230
231        match filemeta_error {
232            Error::Io(io_error) => {
233                assert!(io_error.to_string().contains(custom_error));
234                assert_eq!(io_error.kind(), ErrorKind::Other);
235            }
236            _ => panic!("Expected Io variant"),
237        }
238    }
239
240    #[test]
241    fn test_filemeta_error_conversions() {
242        // Test various error conversions
243        let serde_decode_err =
244            rmp_serde::decode::Error::InvalidMarkerRead(std::io::Error::new(ErrorKind::InvalidData, "invalid"));
245        let filemeta_error: Error = serde_decode_err.into();
246        assert!(matches!(filemeta_error, Error::RmpSerdeDecode(_)));
247
248        // Test with string-based error that we can actually create
249        let encode_error_string = "test encode error";
250        let filemeta_error = Error::RmpSerdeEncode(encode_error_string.to_string());
251        assert!(matches!(filemeta_error, Error::RmpSerdeEncode(_)));
252
253        let utf8_err = std::string::String::from_utf8(vec![0xFF]).unwrap_err();
254        let filemeta_error: Error = utf8_err.into();
255        assert!(matches!(filemeta_error, Error::FromUtf8(_)));
256    }
257
258    #[test]
259    fn test_filemeta_error_clone() {
260        let test_cases = vec![
261            Error::FileNotFound,
262            Error::FileVersionNotFound,
263            Error::VolumeNotFound,
264            Error::FileCorrupt,
265            Error::DoneForNow,
266            Error::MethodNotAllowed,
267            Error::Unexpected,
268            Error::Io(IoError::new(ErrorKind::NotFound, "test")),
269            Error::RmpSerdeDecode("test decode error".to_string()),
270            Error::RmpSerdeEncode("test encode error".to_string()),
271            Error::FromUtf8("test utf8 error".to_string()),
272            Error::RmpDecodeValueRead("test value read error".to_string()),
273            Error::RmpEncodeValueWrite("test value write error".to_string()),
274            Error::RmpDecodeNumValueRead("test num read error".to_string()),
275            Error::RmpDecodeMarkerRead("test marker read error".to_string()),
276            Error::TimeComponentRange("test time error".to_string()),
277            Error::UuidParse("test uuid error".to_string()),
278        ];
279
280        for original_error in test_cases {
281            let cloned_error = original_error.clone();
282            assert_eq!(original_error, cloned_error);
283        }
284    }
285
286    #[test]
287    fn test_filemeta_error_partial_eq() {
288        // Test equality for simple variants
289        assert_eq!(Error::FileNotFound, Error::FileNotFound);
290        assert_ne!(Error::FileNotFound, Error::FileVersionNotFound);
291
292        // Test equality for Io variants
293        let io1 = Error::Io(IoError::new(ErrorKind::NotFound, "test"));
294        let io2 = Error::Io(IoError::new(ErrorKind::NotFound, "test"));
295        let io3 = Error::Io(IoError::new(ErrorKind::PermissionDenied, "test"));
296        assert_eq!(io1, io2);
297        assert_ne!(io1, io3);
298
299        // Test equality for string variants
300        let decode1 = Error::RmpSerdeDecode("error message".to_string());
301        let decode2 = Error::RmpSerdeDecode("error message".to_string());
302        let decode3 = Error::RmpSerdeDecode("different message".to_string());
303        assert_eq!(decode1, decode2);
304        assert_ne!(decode1, decode3);
305    }
306
307    #[test]
308    fn test_filemeta_error_display() {
309        let test_cases = vec![
310            (Error::FileNotFound, "File not found"),
311            (Error::FileVersionNotFound, "File version not found"),
312            (Error::VolumeNotFound, "Volume not found"),
313            (Error::FileCorrupt, "File corrupt"),
314            (Error::DoneForNow, "Done for now"),
315            (Error::MethodNotAllowed, "Method not allowed"),
316            (Error::Unexpected, "Unexpected error"),
317            (Error::RmpSerdeDecode("test".to_string()), "rmp serde decode error: test"),
318            (Error::RmpSerdeEncode("test".to_string()), "rmp serde encode error: test"),
319            (Error::FromUtf8("test".to_string()), "Invalid UTF-8: test"),
320            (Error::TimeComponentRange("test".to_string()), "time component range error: test"),
321            (Error::UuidParse("test".to_string()), "uuid parse error: test"),
322        ];
323
324        for (error, expected_message) in test_cases {
325            assert_eq!(error.to_string(), expected_message);
326        }
327    }
328
329    #[test]
330    fn test_rmp_conversions() {
331        // Test rmp value read error (this one works since it has the same signature)
332        let value_read_err = rmp::decode::ValueReadError::InvalidMarkerRead(std::io::Error::new(ErrorKind::InvalidData, "test"));
333        let filemeta_error: Error = value_read_err.into();
334        assert!(matches!(filemeta_error, Error::RmpDecodeValueRead(_)));
335
336        // Test rmp num value read error
337        let num_value_err =
338            rmp::decode::NumValueReadError::InvalidMarkerRead(std::io::Error::new(ErrorKind::InvalidData, "test"));
339        let filemeta_error: Error = num_value_err.into();
340        assert!(matches!(filemeta_error, Error::RmpDecodeNumValueRead(_)));
341    }
342
343    #[test]
344    fn test_time_and_uuid_conversions() {
345        // Test time component range error
346        use time::{Date, Month};
347        let time_result = Date::from_calendar_date(2023, Month::January, 32); // Invalid day
348        assert!(time_result.is_err());
349        let time_error = time_result.unwrap_err();
350        let filemeta_error: Error = time_error.into();
351        assert!(matches!(filemeta_error, Error::TimeComponentRange(_)));
352
353        // Test UUID parse error
354        let uuid_result = uuid::Uuid::parse_str("invalid-uuid");
355        assert!(uuid_result.is_err());
356        let uuid_error = uuid_result.unwrap_err();
357        let filemeta_error: Error = uuid_error.into();
358        assert!(matches!(filemeta_error, Error::UuidParse(_)));
359    }
360
361    #[test]
362    fn test_marker_read_error_conversion() {
363        // Test rmp marker read error conversion
364        let marker_err = rmp::decode::MarkerReadError(std::io::Error::new(ErrorKind::InvalidData, "marker test"));
365        let filemeta_error: Error = marker_err.into();
366        assert!(matches!(filemeta_error, Error::RmpDecodeMarkerRead(_)));
367        assert!(filemeta_error.to_string().contains("marker"));
368    }
369
370    #[test]
371    fn test_is_io_eof_function() {
372        // Test is_io_eof helper function
373        let eof_error = Error::Io(IoError::new(ErrorKind::UnexpectedEof, "eof"));
374        assert!(is_io_eof(&eof_error));
375
376        let not_eof_error = Error::Io(IoError::new(ErrorKind::NotFound, "not found"));
377        assert!(!is_io_eof(&not_eof_error));
378
379        let non_io_error = Error::FileNotFound;
380        assert!(!is_io_eof(&non_io_error));
381    }
382
383    #[test]
384    fn test_filemeta_error_to_io_error_conversion() {
385        // Test conversion from FileMeta Error to io::Error through other function
386        let original_io_error = IoError::new(ErrorKind::InvalidData, "test data");
387        let filemeta_error = Error::other(original_io_error);
388
389        match filemeta_error {
390            Error::Io(io_err) => {
391                assert_eq!(io_err.kind(), ErrorKind::Other);
392                assert!(io_err.to_string().contains("test data"));
393            }
394            _ => panic!("Expected Io variant"),
395        }
396    }
397
398    #[test]
399    fn test_filemeta_error_roundtrip_conversion() {
400        // Test roundtrip conversion: io::Error -> FileMeta Error -> io::Error
401        let original_io_error = IoError::new(ErrorKind::PermissionDenied, "permission test");
402
403        // Convert to FileMeta Error
404        let filemeta_error: Error = original_io_error.into();
405
406        // Extract the io::Error back
407        match filemeta_error {
408            Error::Io(extracted_io_error) => {
409                assert_eq!(extracted_io_error.kind(), ErrorKind::PermissionDenied);
410                assert!(extracted_io_error.to_string().contains("permission test"));
411            }
412            _ => panic!("Expected Io variant"),
413        }
414    }
415
416    #[test]
417    fn test_filemeta_error_io_error_kinds_preservation() {
418        let io_error_kinds = vec![
419            ErrorKind::NotFound,
420            ErrorKind::PermissionDenied,
421            ErrorKind::ConnectionRefused,
422            ErrorKind::ConnectionReset,
423            ErrorKind::ConnectionAborted,
424            ErrorKind::NotConnected,
425            ErrorKind::AddrInUse,
426            ErrorKind::AddrNotAvailable,
427            ErrorKind::BrokenPipe,
428            ErrorKind::AlreadyExists,
429            ErrorKind::WouldBlock,
430            ErrorKind::InvalidInput,
431            ErrorKind::InvalidData,
432            ErrorKind::TimedOut,
433            ErrorKind::WriteZero,
434            ErrorKind::Interrupted,
435            ErrorKind::UnexpectedEof,
436            ErrorKind::Other,
437        ];
438
439        for kind in io_error_kinds {
440            let io_error = IoError::new(kind, format!("test error for {kind:?}"));
441            let filemeta_error: Error = io_error.into();
442
443            match filemeta_error {
444                Error::Unexpected => {
445                    assert_eq!(kind, ErrorKind::UnexpectedEof);
446                }
447                Error::Io(extracted_io_error) => {
448                    assert_eq!(extracted_io_error.kind(), kind);
449                    assert!(extracted_io_error.to_string().contains("test error"));
450                }
451                _ => panic!("Expected Io variant for kind {kind:?}"),
452            }
453        }
454    }
455
456    #[test]
457    fn test_filemeta_error_downcast_chain() {
458        // Test error downcast chain functionality
459        let original_io_error = IoError::new(ErrorKind::InvalidData, "original error");
460        let filemeta_error = Error::other(original_io_error);
461
462        // The error should be wrapped as an Io variant
463        if let Error::Io(io_err) = filemeta_error {
464            // The wrapped error should be Other kind (from std::io::Error::other)
465            assert_eq!(io_err.kind(), ErrorKind::Other);
466            // But the message should still contain the original error information
467            assert!(io_err.to_string().contains("original error"));
468        } else {
469            panic!("Expected Io variant");
470        }
471    }
472
473    #[test]
474    fn test_filemeta_error_maintains_error_information() {
475        let test_cases = vec![
476            (ErrorKind::NotFound, "file not found"),
477            (ErrorKind::PermissionDenied, "access denied"),
478            (ErrorKind::InvalidData, "corrupt data"),
479            (ErrorKind::TimedOut, "operation timed out"),
480        ];
481
482        for (kind, message) in test_cases {
483            let io_error = IoError::new(kind, message);
484            let error_message = io_error.to_string();
485            let filemeta_error: Error = io_error.into();
486
487            match filemeta_error {
488                Error::Io(extracted_io_error) => {
489                    assert_eq!(extracted_io_error.kind(), kind);
490                    assert_eq!(extracted_io_error.to_string(), error_message);
491                }
492                _ => panic!("Expected Io variant"),
493            }
494        }
495    }
496
497    #[test]
498    fn test_filemeta_error_complex_conversion_chain() {
499        // Test conversion from string error types that we can actually create
500
501        // Test with UUID error conversion
502        let uuid_result = uuid::Uuid::parse_str("invalid-uuid-format");
503        assert!(uuid_result.is_err());
504        let uuid_error = uuid_result.unwrap_err();
505        let filemeta_error: Error = uuid_error.into();
506
507        match filemeta_error {
508            Error::UuidParse(message) => {
509                assert!(message.contains("invalid"));
510            }
511            _ => panic!("Expected UuidParse variant"),
512        }
513
514        // Test with time error conversion
515        use time::{Date, Month};
516        let time_result = Date::from_calendar_date(2023, Month::January, 32); // Invalid day
517        assert!(time_result.is_err());
518        let time_error = time_result.unwrap_err();
519        let filemeta_error2: Error = time_error.into();
520
521        match filemeta_error2 {
522            Error::TimeComponentRange(message) => {
523                assert!(message.contains("range"));
524            }
525            _ => panic!("Expected TimeComponentRange variant"),
526        }
527
528        // Test with UTF8 error conversion
529        let utf8_result = std::string::String::from_utf8(vec![0xFF]);
530        assert!(utf8_result.is_err());
531        let utf8_error = utf8_result.unwrap_err();
532        let filemeta_error3: Error = utf8_error.into();
533
534        match filemeta_error3 {
535            Error::FromUtf8(message) => {
536                assert!(message.contains("utf"));
537            }
538            _ => panic!("Expected FromUtf8 variant"),
539        }
540    }
541
542    #[test]
543    fn test_filemeta_error_equality_with_io_errors() {
544        // Test equality comparison for Io variants
545        let io_error1 = IoError::new(ErrorKind::NotFound, "test message");
546        let io_error2 = IoError::new(ErrorKind::NotFound, "test message");
547        let io_error3 = IoError::new(ErrorKind::PermissionDenied, "test message");
548        let io_error4 = IoError::new(ErrorKind::NotFound, "different message");
549
550        let filemeta_error1 = Error::Io(io_error1);
551        let filemeta_error2 = Error::Io(io_error2);
552        let filemeta_error3 = Error::Io(io_error3);
553        let filemeta_error4 = Error::Io(io_error4);
554
555        // Same kind and message should be equal
556        assert_eq!(filemeta_error1, filemeta_error2);
557
558        // Different kinds should not be equal
559        assert_ne!(filemeta_error1, filemeta_error3);
560
561        // Different messages should not be equal
562        assert_ne!(filemeta_error1, filemeta_error4);
563    }
564
565    #[test]
566    fn test_filemeta_error_clone_io_variants() {
567        let io_error = IoError::new(ErrorKind::ConnectionReset, "connection lost");
568        let original_error = Error::Io(io_error);
569        let cloned_error = original_error.clone();
570
571        // Cloned error should be equal to original
572        assert_eq!(original_error, cloned_error);
573
574        // Both should maintain the same properties
575        match (original_error, cloned_error) {
576            (Error::Io(orig_io), Error::Io(cloned_io)) => {
577                assert_eq!(orig_io.kind(), cloned_io.kind());
578                assert_eq!(orig_io.to_string(), cloned_io.to_string());
579            }
580            _ => panic!("Both should be Io variants"),
581        }
582    }
583}