find_identical_files/
error.rs

1use crate::traits::Colors;
2use rust_xlsxwriter::XlsxError;
3use std::{io, path::PathBuf};
4use thiserror::Error;
5
6/**
7Result type to simplify function signatures.
8
9This is a custom result type that uses our custom `FIFError` for the error type.
10
11Functions can return `FIFResult<T>` and then use `?` to automatically propagate errors.
12*/
13pub type FIFResult<T> = Result<T, FIFError>;
14
15/// FIF Error enum
16///
17/// The `FIFError` enum defines the error values
18///
19/// <https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/define_error_type.html>
20#[derive(Error, Debug)]
21pub enum FIFError {
22    // Errors encountered while parsing CSV data (e.g., inconsistent columns, invalid data).
23    #[error("{msg}: '{0}'", msg = "CSV Parsing Error".red().bold())]
24    CSVError(#[from] csv::Error),
25
26    /// Specific error when a file is not found.
27    #[error("{msg}: '{path:?}'\nPerhaps some temporary files no longer exist!", msg = "File Not Found Error".red().bold())]
28    FileNotFound { path: PathBuf },
29
30    /// Generic file opening error with more context.
31    #[error("{msg}: '{path:?}'\n{io_error}", msg = "File Open Error".red().bold())]
32    FileOpenError {
33        path: PathBuf,
34        #[source]
35        io_error: io::Error,
36    },
37
38    /// Standard I/O error wrapper.
39    #[error("{msg}: '{0}'", msg = "IO Error".red().bold())]
40    Io(#[from] io::Error),
41
42    /// Error when a JSON serialization or deserialization operation fails.
43    #[error("{msg}: '{0}'", msg = "JSON Serialization/Deserialization Error".red().bold())]
44    Json(#[from] serde_json::Error),
45
46    /// Error when a Yaml serialization or deserialization operation fails.
47    #[error("{msg}: '{0}'", msg = "YAML Serialization/Deserialization Error".red().bold())]
48    Yaml(#[from] serde_yaml::Error),
49
50    /// Specific error when file permission is denied.
51    #[error("{msg}: '{path:?}'", msg = "Permission Denied Error".red().bold())]
52    PermissionDenied { path: PathBuf },
53
54    /// XlsxError wrapper.
55    #[error("{msg}: '{0}'", msg = "XLSX Error".red().bold())]
56    XlsxError(#[from] XlsxError),
57}
58
59#[cfg(test)]
60mod error_tests {
61    use super::*;
62    use std::io::ErrorKind;
63
64    #[test]
65    fn test_file_not_found_error_message() {
66        let path = PathBuf::from("/non/existent/foo/file.txt");
67        let error = FIFError::FileNotFound { path: path.clone() };
68
69        let msg = "File Not Found Error".red().bold();
70        let expected_msg = format!(
71            "{msg}: '{:?}'\nPerhaps some temporary files no longer exist!",
72            path
73        );
74        assert_eq!(format!("{}", error), expected_msg);
75    }
76
77    #[test]
78    fn test_file_open_error_message() {
79        let path = PathBuf::from("/var/log/some_protected_file.log");
80        let io_error = io::Error::new(ErrorKind::PermissionDenied, "permission denied");
81        let error = FIFError::FileOpenError {
82            path: path.clone(),
83            io_error,
84        };
85
86        let msg = "File Open Error".red().bold();
87        let expected_msg = format!("{msg}: '{:?}'\npermission denied", path);
88        assert_eq!(format!("{}", error), expected_msg);
89    }
90
91    #[test]
92    fn test_csv_error_message() {
93        // Create a std::io::Error from the ErrorKind, then convert it to csv::Error
94        let io_error = io::Error::new(ErrorKind::InvalidData, "invalid data");
95        let csv_error = csv::Error::from(io_error);
96        let error = FIFError::CSVError(csv_error);
97
98        let msg = "CSV Parsing Error".red().bold();
99        let expected_msg = format!("{msg}: 'invalid data'");
100        assert_eq!(format!("{}", error), expected_msg);
101    }
102
103    #[test]
104    fn test_permission_denied_error_message() {
105        let path = PathBuf::from("/etc/passwd");
106        let error = FIFError::PermissionDenied { path: path.clone() };
107
108        let msg = "Permission Denied Error".red().bold();
109        let expected_msg = format!("{msg}: '{path:?}'");
110        assert_eq!(format!("{}", error), expected_msg);
111    }
112
113    #[test]
114    fn test_io_error_message() {
115        let io_error = io::Error::other("something went wrong");
116        let error = FIFError::Io(io_error);
117
118        let msg = "IO Error".red().bold();
119        let expected_msg = format!("{msg}: 'something went wrong'");
120        assert_eq!(format!("{}", error), expected_msg);
121    }
122}