refactor: improve code formatting in `mod.rs` and `logger.rs`
[ouch.git] / src / error.rs
blob6b2e92d98f853f578b3598276842717ad6cf6faa
1 //! Error types definitions.
2 //!
3 //! All usage errors will pass through the Error enum, a lot of them in the Error::Custom.
5 use std::{
6     borrow::Cow,
7     ffi::OsString,
8     fmt::{self, Display},
9     io,
12 use crate::{
13     accessible::is_running_in_accessible_mode,
14     extension::{PRETTY_SUPPORTED_ALIASES, PRETTY_SUPPORTED_EXTENSIONS},
15     utils::os_str_to_str,
18 /// All errors that can be generated by `ouch`
19 #[derive(Debug, Clone)]
20 pub enum Error {
21     /// An IoError that doesn't have a dedicated error variant
22     IoError { reason: String },
23     /// From lzzzz::lz4f::Error
24     Lz4Error { reason: String },
25     /// Detected from io::Error if .kind() is io::ErrorKind::NotFound
26     NotFound { error_title: String },
27     /// NEEDS MORE CONTEXT
28     AlreadyExists { error_title: String },
29     /// From zip::result::ZipError::InvalidArchive
30     InvalidZipArchive(&'static str),
31     /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
32     PermissionDenied { error_title: String },
33     /// From zip::result::ZipError::UnsupportedArchive
34     UnsupportedZipArchive(&'static str),
35     /// We don't support compressing the root folder.
36     CompressingRootFolder,
37     /// Specialized walkdir's io::Error wrapper with additional information on the error
38     WalkdirError { reason: String },
39     /// Custom and unique errors are reported in this variant
40     Custom { reason: FinalError },
41     /// Invalid format passed to `--format`
42     InvalidFormatFlag { text: OsString, reason: String },
43     /// From sevenz_rust::Error
44     SevenzipError { reason: String },
45     /// Recognised but unsupported format
46     // currently only RAR when built without the `unrar` feature
47     UnsupportedFormat { reason: String },
48     /// Invalid password provided
49     InvalidPassword { reason: String },
52 /// Alias to std's Result with ouch's Error
53 pub type Result<T> = std::result::Result<T, Error>;
55 /// A string either heap-allocated or located in static storage
56 pub type CowStr = Cow<'static, str>;
58 /// Pretty final error message for end users, crashing the program after display.
59 #[derive(Clone, Debug, Default, PartialEq, Eq)]
60 pub struct FinalError {
61     /// Should be made of just one line, appears after the "\[ERROR\]" part
62     title: CowStr,
63     /// Shown as a unnumbered list in yellow
64     details: Vec<CowStr>,
65     /// Shown as green at the end to give hints on how to work around this error, if it's fixable
66     hints: Vec<CowStr>,
69 impl Display for FinalError {
70     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71         use crate::utils::colors::*;
73         // Title
74         //
75         // When in ACCESSIBLE mode, the square brackets are suppressed
76         if is_running_in_accessible_mode() {
77             write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
78         } else {
79             write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
80         }
82         // Details
83         for detail in &self.details {
84             write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
85         }
87         // Hints
88         if !self.hints.is_empty() {
89             // Separate by one blank line.
90             writeln!(f)?;
91             // to reduce redundant output for text-to-speech systems, braille
92             // displays and so on, only print "hints" once in ACCESSIBLE mode
93             if is_running_in_accessible_mode() {
94                 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
95                 for hint in &self.hints {
96                     write!(f, "\n{hint}")?;
97                 }
98             } else {
99                 for hint in &self.hints {
100                     write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
101                 }
102             }
103         }
105         Ok(())
106     }
109 impl FinalError {
110     /// Only constructor
111     #[must_use]
112     pub fn with_title(title: impl Into<CowStr>) -> Self {
113         Self {
114             title: title.into(),
115             details: vec![],
116             hints: vec![],
117         }
118     }
120     /// Add one detail line, can have multiple
121     #[must_use]
122     pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
123         self.details.push(detail.into());
124         self
125     }
127     /// Add one hint line, can have multiple
128     #[must_use]
129     pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
130         self.hints.push(hint.into());
131         self
132     }
134     /// Adds all supported formats as hints.
135     ///
136     /// This is what it looks like:
137     /// ```
138     /// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst
139     /// hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
140     /// ```
141     pub fn hint_all_supported_formats(self) -> Self {
142         self.hint(format!("Supported extensions are: {}", PRETTY_SUPPORTED_EXTENSIONS))
143             .hint(format!("Supported aliases are: {}", PRETTY_SUPPORTED_ALIASES))
144     }
147 impl From<Error> for FinalError {
148     fn from(err: Error) -> Self {
149         match err {
150             Error::WalkdirError { reason } => FinalError::with_title(reason),
151             Error::NotFound { error_title } => FinalError::with_title(error_title).detail("File not found"),
152             Error::CompressingRootFolder => {
153                 FinalError::with_title("It seems you're trying to compress the root folder.")
154                     .detail("This is unadvisable since ouch does compressions in-memory.")
155                     .hint("Use a more appropriate tool for this, such as rsync.")
156             }
157             Error::IoError { reason } => FinalError::with_title(reason),
158             Error::Lz4Error { reason } => FinalError::with_title(reason),
159             Error::AlreadyExists { error_title } => FinalError::with_title(error_title).detail("File already exists"),
160             Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(reason),
161             Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
162             Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(reason),
163             Error::InvalidFormatFlag { reason, text } => {
164                 FinalError::with_title(format!("Failed to parse `--format {}`", os_str_to_str(&text)))
165                     .detail(reason)
166                     .hint_all_supported_formats()
167                     .hint("")
168                     .hint("Examples:")
169                     .hint("  --format tar")
170                     .hint("  --format gz")
171                     .hint("  --format tar.gz")
172             }
173             Error::Custom { reason } => reason.clone(),
174             Error::SevenzipError { reason } => FinalError::with_title("7z error").detail(reason),
175             Error::UnsupportedFormat { reason } => {
176                 FinalError::with_title("Recognised but unsupported format").detail(reason.clone())
177             }
178             Error::InvalidPassword { reason } => FinalError::with_title("Invalid password").detail(reason.clone()),
179         }
180     }
183 impl fmt::Display for Error {
184     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185         let err = FinalError::from(self.clone());
186         write!(f, "{err}")
187     }
190 impl From<std::io::Error> for Error {
191     fn from(err: std::io::Error) -> Self {
192         let error_title = err.to_string();
194         match err.kind() {
195             io::ErrorKind::NotFound => Self::NotFound { error_title },
196             io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title },
197             io::ErrorKind::AlreadyExists => Self::AlreadyExists { error_title },
198             _other => Self::IoError { reason: error_title },
199         }
200     }
203 impl From<bzip3::Error> for Error {
204     fn from(err: bzip3::Error) -> Self {
205         use bzip3::Error as Bz3Error;
206         match err {
207             Bz3Error::Io(inner) => inner.into(),
208             Bz3Error::BlockSize | Bz3Error::ProcessBlock(_) | Bz3Error::InvalidSignature => {
209                 FinalError::with_title("bzip3 error").detail(err.to_string()).into()
210             }
211         }
212     }
215 impl From<zip::result::ZipError> for Error {
216     fn from(err: zip::result::ZipError) -> Self {
217         use zip::result::ZipError;
218         match err {
219             ZipError::Io(io_err) => Self::from(io_err),
220             ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
221             ZipError::FileNotFound => Self::Custom {
222                 reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
223             },
224             ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
225         }
226     }
229 #[cfg(feature = "unrar")]
230 impl From<unrar::error::UnrarError> for Error {
231     fn from(err: unrar::error::UnrarError) -> Self {
232         Self::Custom {
233             reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)),
234         }
235     }
238 impl From<sevenz_rust::Error> for Error {
239     fn from(err: sevenz_rust::Error) -> Self {
240         Self::SevenzipError {
241             reason: err.to_string(),
242         }
243     }
246 impl From<ignore::Error> for Error {
247     fn from(err: ignore::Error) -> Self {
248         Self::WalkdirError {
249             reason: err.to_string(),
250         }
251     }
254 impl From<FinalError> for Error {
255     fn from(err: FinalError) -> Self {
256         Self::Custom { reason: err }
257     }