Update CHANGELOG.md
[ouch.git] / src / error.rs
bloba54d0a2cc6a5c6f47487e340698f0c11c3174ab6
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     fmt::{self, Display},
8 };
10 use crate::{accessible::is_running_in_accessible_mode, utils::colors::*};
12 /// All errors that can be generated by `ouch`
13 #[derive(Debug)]
14 pub enum Error {
15     /// Not every IoError, some of them get filtered by `From<io::Error>` into other variants
16     IoError { reason: String },
17     /// From lzzzz::lz4f::Error
18     Lz4Error { reason: String },
19     /// Detected from io::Error if .kind() is io::ErrorKind::NotFound
20     NotFound { error_title: String },
21     /// NEEDS MORE CONTEXT
22     AlreadyExists { error_title: String },
23     /// From zip::result::ZipError::InvalidArchive
24     InvalidZipArchive(&'static str),
25     /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
26     PermissionDenied { error_title: String },
27     /// From zip::result::ZipError::UnsupportedArchive
28     UnsupportedZipArchive(&'static str),
29     /// TO BE REMOVED
30     CompressingRootFolder,
31     /// Specialized walkdir's io::Error wrapper with additional information on the error
32     WalkdirError { reason: String },
33     /// Custom and unique errors are reported in this variant
34     Custom { reason: FinalError },
35     /// Invalid format passed to `--format`
36     InvalidFormat { reason: String },
37     /// From sevenz_rust::Error
38     SevenzipError(sevenz_rust::Error),
39     /// Recognised but unsupported format
40     // currently only RAR when built without the `unrar` feature
41     UnsupportedFormat { reason: String },
44 /// Alias to std's Result with ouch's Error
45 pub type Result<T> = std::result::Result<T, Error>;
47 /// A string either heap-allocated or located in static storage
48 pub type CowStr = Cow<'static, str>;
50 /// Pretty final error message for end users, crashing the program after display.
51 #[derive(Clone, Debug, Default, PartialEq, Eq)]
52 pub struct FinalError {
53     /// Should be made of just one line, appears after the "\[ERROR\]" part
54     title: CowStr,
55     /// Shown as a unnumbered list in yellow
56     details: Vec<CowStr>,
57     /// Shown as green at the end to give hints on how to work around this error, if it's fixable
58     hints: Vec<CowStr>,
61 impl Display for FinalError {
62     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63         // Title
64         //
65         // When in ACCESSIBLE mode, the square brackets are suppressed
66         if is_running_in_accessible_mode() {
67             write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
68         } else {
69             write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
70         }
72         // Details
73         for detail in &self.details {
74             write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
75         }
77         // Hints
78         if !self.hints.is_empty() {
79             // Separate by one blank line.
80             writeln!(f)?;
81             // to reduce redundant output for text-to-speech systems, braille
82             // displays and so on, only print "hints" once in ACCESSIBLE mode
83             if is_running_in_accessible_mode() {
84                 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
85                 for hint in &self.hints {
86                     write!(f, "\n{hint}")?;
87                 }
88             } else {
89                 for hint in &self.hints {
90                     write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
91                 }
92             }
93         }
95         Ok(())
96     }
99 impl FinalError {
100     /// Only constructor
101     #[must_use]
102     pub fn with_title(title: impl Into<CowStr>) -> Self {
103         Self {
104             title: title.into(),
105             details: vec![],
106             hints: vec![],
107         }
108     }
110     /// Add one detail line, can have multiple
111     #[must_use]
112     pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
113         self.details.push(detail.into());
114         self
115     }
117     /// Add one hint line, can have multiple
118     #[must_use]
119     pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
120         self.hints.push(hint.into());
121         self
122     }
125 impl fmt::Display for Error {
126     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127         let err = match self {
128             Error::WalkdirError { reason } => FinalError::with_title(reason.to_string()),
129             Error::NotFound { error_title } => FinalError::with_title(error_title.to_string()).detail("File not found"),
130             Error::CompressingRootFolder => {
131                 FinalError::with_title("It seems you're trying to compress the root folder.")
132                     .detail("This is unadvisable since ouch does compressions in-memory.")
133                     .hint("Use a more appropriate tool for this, such as rsync.")
134             }
135             Error::IoError { reason } => FinalError::with_title(reason.to_string()),
136             Error::Lz4Error { reason } => FinalError::with_title(reason.to_string()),
137             Error::AlreadyExists { error_title } => {
138                 FinalError::with_title(error_title.to_string()).detail("File already exists")
139             }
140             Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(*reason),
141             Error::PermissionDenied { error_title } => {
142                 FinalError::with_title(error_title.to_string()).detail("Permission denied")
143             }
144             Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(*reason),
145             Error::InvalidFormat { reason } => FinalError::with_title("Invalid archive format").detail(reason.clone()),
146             Error::Custom { reason } => reason.clone(),
147             Error::SevenzipError(reason) => FinalError::with_title("7z error").detail(reason.to_string()),
148             Error::UnsupportedFormat { reason } => {
149                 FinalError::with_title("Recognised but unsupported format").detail(reason.clone())
150             }
151         };
153         write!(f, "{err}")
154     }
157 impl From<std::io::Error> for Error {
158     fn from(err: std::io::Error) -> Self {
159         match err.kind() {
160             std::io::ErrorKind::NotFound => Self::NotFound {
161                 error_title: err.to_string(),
162             },
163             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied {
164                 error_title: err.to_string(),
165             },
166             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists {
167                 error_title: err.to_string(),
168             },
169             _other => Self::IoError {
170                 reason: err.to_string(),
171             },
172         }
173     }
176 impl From<zip::result::ZipError> for Error {
177     fn from(err: zip::result::ZipError) -> Self {
178         use zip::result::ZipError;
179         match err {
180             ZipError::Io(io_err) => Self::from(io_err),
181             ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
182             ZipError::FileNotFound => Self::Custom {
183                 reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
184             },
185             ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
186         }
187     }
190 #[cfg(feature = "unrar")]
191 impl From<unrar::error::UnrarError> for Error {
192     fn from(err: unrar::error::UnrarError) -> Self {
193         Self::Custom {
194             reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)),
195         }
196     }
199 impl From<sevenz_rust::Error> for Error {
200     fn from(err: sevenz_rust::Error) -> Self {
201         Self::SevenzipError(err)
202     }
205 impl From<ignore::Error> for Error {
206     fn from(err: ignore::Error) -> Self {
207         Self::WalkdirError {
208             reason: err.to_string(),
209         }
210     }
213 impl From<FinalError> for Error {
214     fn from(err: FinalError) -> Self {
215         Self::Custom { reason: err }
216     }