Merge pull request #253 from Crypto-Spartan/update-dependencies
[ouch.git] / src / error.rs
blob34a9c9910813796bf8b519fad0ac952aa77218ea
1 //! Error types definitions.
2 //!
3 //! All usage errors will pass throught the Error enum, a lot of them in the Error::Custom.
5 use std::{
6     borrow::Cow,
7     fmt::{self, Display},
8 };
10 use crate::utils::colors::*;
12 #[allow(missing_docs)]
13 /// All errors that can be generated by `ouch`
14 #[derive(Debug, PartialEq)]
15 pub enum Error {
16     /// Not every IoError, some of them get filtered by `From<io::Error>` into other variants
17     IoError { reason: String },
18     /// From lzzzz::lz4f::Error
19     Lz4Error { reason: String },
20     /// Detected from io::Error if .kind() is io::ErrorKind::NotFound
21     NotFound { error_title: String },
22     /// NEEDS MORE CONTEXT
23     AlreadyExists { error_title: String },
24     /// From zip::result::ZipError::InvalidArchive
25     InvalidZipArchive(&'static str),
26     /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
27     PermissionDenied { error_title: String },
28     /// From zip::result::ZipError::UnsupportedArchive
29     UnsupportedZipArchive(&'static str),
30     /// TO BE REMOVED
31     CompressingRootFolder,
32     /// Specialized walkdir's io::Error wrapper with additional information on the error
33     WalkdirError { reason: String },
34     /// Custom and unique errors are reported in this variant
35     Custom { reason: FinalError },
38 /// Alias to std's Result with ouch's Error
39 pub type Result<T> = std::result::Result<T, Error>;
41 /// A string either heap-allocated or located in static storage
42 pub type CowStr = Cow<'static, str>;
44 /// Pretty final error message for end users, crashing the program after display.
45 #[derive(Clone, Debug, Default, PartialEq)]
46 pub struct FinalError {
47     /// Should be made of just one line, appears after the "\[ERROR\]" part
48     title: CowStr,
49     /// Shown as a unnumbered list in yellow
50     details: Vec<CowStr>,
51     /// Shown as green at the end to give hints on how to work around this error, if it's fixable
52     hints: Vec<CowStr>,
55 impl Display for FinalError {
56     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57         // Title
58         //
59         // When in ACCESSIBLE mode, the square brackets are suppressed
60         if *crate::cli::ACCESSIBLE.get().unwrap_or(&false) {
61             write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
62         } else {
63             write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
64         }
66         // Details
67         for detail in &self.details {
68             write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
69         }
71         // Hints
72         if !self.hints.is_empty() {
73             // Separate by one blank line.
74             writeln!(f)?;
75             // to reduce redundant output for text-to-speach systems, braille
76             // displays and so on, only print "hints" once in ACCESSIBLE mode
77             if *crate::cli::ACCESSIBLE.get().unwrap_or(&false) {
78                 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
79                 for hint in &self.hints {
80                     write!(f, "\n{}", hint)?;
81                 }
82             } else {
83                 for hint in &self.hints {
84                     write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
85                 }
86             }
87         }
89         Ok(())
90     }
93 impl FinalError {
94     /// Only constructor
95     #[must_use]
96     pub fn with_title(title: impl Into<CowStr>) -> Self {
97         Self { title: title.into(), details: vec![], hints: vec![] }
98     }
100     /// Add one detail line, can have multiple
101     #[must_use]
102     pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
103         self.details.push(detail.into());
104         self
105     }
107     /// Add one hint line, can have multiple
108     #[must_use]
109     pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
110         self.hints.push(hint.into());
111         self
112     }
115 impl fmt::Display for Error {
116     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117         let err = match self {
118             Error::WalkdirError { reason } => FinalError::with_title(reason.to_string()),
119             Error::NotFound { error_title } => FinalError::with_title(error_title.to_string()).detail("File not found"),
120             Error::CompressingRootFolder => {
121                 FinalError::with_title("It seems you're trying to compress the root folder.")
122                     .detail("This is unadvisable since ouch does compressions in-memory.")
123                     .hint("Use a more appropriate tool for this, such as rsync.")
124             }
125             Error::IoError { reason } => FinalError::with_title(reason.to_string()),
126             Error::Lz4Error { reason } => FinalError::with_title(reason.to_string()),
127             Error::AlreadyExists { error_title } => {
128                 FinalError::with_title(error_title.to_string()).detail("File already exists")
129             }
130             Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(*reason),
131             Error::PermissionDenied { error_title } => {
132                 FinalError::with_title(error_title.to_string()).detail("Permission denied")
133             }
134             Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(*reason),
135             Error::Custom { reason } => reason.clone(),
136         };
138         write!(f, "{}", err)
139     }
142 impl From<std::io::Error> for Error {
143     fn from(err: std::io::Error) -> Self {
144         match err.kind() {
145             std::io::ErrorKind::NotFound => Self::NotFound { error_title: err.to_string() },
146             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title: err.to_string() },
147             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists { error_title: err.to_string() },
148             _other => Self::IoError { reason: err.to_string() },
149         }
150     }
153 impl From<lzzzz::lz4f::Error> for Error {
154     fn from(err: lzzzz::lz4f::Error) -> Self {
155         Self::Lz4Error { reason: err.to_string() }
156     }
159 impl From<zip::result::ZipError> for Error {
160     fn from(err: zip::result::ZipError) -> Self {
161         use zip::result::ZipError;
162         match err {
163             ZipError::Io(io_err) => Self::from(io_err),
164             ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
165             ZipError::FileNotFound => {
166                 Self::Custom {
167                     reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
168                 }
169             }
170             ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
171         }
172     }
175 impl From<ignore::Error> for Error {
176     fn from(err: ignore::Error) -> Self {
177         Self::WalkdirError { reason: err.to_string() }
178     }
181 impl From<FinalError> for Error {
182     fn from(err: FinalError) -> Self {
183         Self::Custom { reason: err }
184     }