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