apply clippy lints and small refactors (#86)
[ouch.git] / src / error.rs
blobcaf7e6cfe9596ff6edac3b0bd9ed7d3752d821fa
1 //! Error type definitions.
2 //!
3 //! All the unexpected user-side errors should be treated in this file, that does not include
4 //! errors made by devs in our implementation.
5 //!
6 //! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function
7 //! calls inside of this module.
9 use std::{
10     fmt::{self, Display},
11     path::{Path, PathBuf},
14 use crate::{oof, utils::colors::*};
16 #[derive(Debug, PartialEq)]
17 pub enum Error {
18     UnknownExtensionError(String),
19     MissingExtensionError(PathBuf),
20     IoError { reason: String },
21     FileNotFound(PathBuf),
22     AlreadyExists,
23     InvalidZipArchive(&'static str),
24     PermissionDenied,
25     UnsupportedZipArchive(&'static str),
26     InternalError,
27     OofError(oof::OofError),
28     CompressingRootFolder,
29     MissingArgumentsForCompression,
30     MissingArgumentsForDecompression,
31     CompressionTypo,
32     WalkdirError { reason: String },
33     Custom { reason: FinalError },
36 pub type Result<T> = std::result::Result<T, Error>;
38 #[derive(Clone, Debug, Default, PartialEq)]
39 pub struct FinalError {
40     title: String,
41     details: Vec<String>,
42     hints: Vec<String>,
45 impl Display for FinalError {
46     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47         // Title
48         writeln!(f, "{}[ERROR]{} {}", red(), reset(), self.title)?;
50         // Details
51         for detail in &self.details {
52             writeln!(f, " {}-{} {}", white(), yellow(), detail)?;
53         }
55         // Hints
56         if !self.hints.is_empty() {
57             // Separate by one blank line.
58             writeln!(f)?;
59             for hint in &self.hints {
60                 writeln!(f, "{}hint:{} {}", green(), reset(), hint)?;
61             }
62         }
64         write!(f, "{}", reset())
65     }
68 impl FinalError {
69     pub fn with_title(title: impl ToString) -> Self {
70         Self { title: title.to_string(), details: vec![], hints: vec![] }
71     }
73     pub fn detail(&mut self, detail: impl ToString) -> &mut Self {
74         self.details.push(detail.to_string());
75         self
76     }
78     pub fn hint(&mut self, hint: impl ToString) -> &mut Self {
79         self.hints.push(hint.to_string());
80         self
81     }
84 impl fmt::Display for Error {
85     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86         let err = match self {
87             Error::MissingExtensionError(filename) => {
88                 let error = FinalError::with_title(format!("Cannot compress to {:?}", filename))
89                     .detail("Ouch could not detect the compression format")
90                     .hint("Use a supported format extension, like '.zip' or '.tar.gz'")
91                     .hint("Check https://github.com/vrmiguel/ouch for a full list of supported formats")
92                     .clone();
94                 error
95             }
96             Error::WalkdirError { reason } => FinalError::with_title(reason),
97             Error::FileNotFound(file) => {
98                 let error = if file == Path::new("") {
99                     FinalError::with_title("file not found!")
100                 } else {
101                     FinalError::with_title(format!("file {:?} not found!", file))
102                 };
104                 error
105             }
106             Error::CompressingRootFolder => {
107                 let error = FinalError::with_title("It seems you're trying to compress the root folder.")
108                     .detail("This is unadvisable since ouch does compressions in-memory.")
109                     .hint("Use a more appropriate tool for this, such as rsync.")
110                     .clone();
112                 error
113             }
114             Error::MissingArgumentsForCompression => {
115                 let error = FinalError::with_title("Could not compress")
116                     .detail("The compress command requires at least 2 arguments")
117                     .hint("You must provide:")
118                     .hint("  - At least one input argument.")
119                     .hint("  - The output argument.")
120                     .hint("")
121                     .hint("Example: `ouch compress image.png img.zip`")
122                     .clone();
124                 error
125             }
126             Error::MissingArgumentsForDecompression => {
127                 let error = FinalError::with_title("Could not decompress")
128                     .detail("The compress command requires at least one argument")
129                     .hint("You must provide:")
130                     .hint("  - At least one input argument.")
131                     .hint("")
132                     .hint("Example: `ouch decompress imgs.tar.gz`")
133                     .clone();
135                 error
136             }
137             Error::InternalError => {
138                 let error = FinalError::with_title("InternalError :(")
139                     .detail("This should not have happened")
140                     .detail("It's probably our fault")
141                     .detail("Please help us improve by reporting the issue at:")
142                     .detail(format!("    {}https://github.com/vrmiguel/ouch/issues ", cyan()))
143                     .clone();
145                 error
146             }
147             Error::OofError(err) => FinalError::with_title(err),
148             Error::IoError { reason } => FinalError::with_title(reason),
149             Error::CompressionTypo => FinalError::with_title("Possible typo detected")
150                 .hint(format!("Did you mean '{}ouch compress{}'?", magenta(), reset()))
151                 .clone(),
152             Error::UnknownExtensionError(_) => todo!(),
153             Error::AlreadyExists => todo!(),
154             Error::InvalidZipArchive(_) => todo!(),
155             Error::PermissionDenied => todo!(),
156             Error::UnsupportedZipArchive(_) => todo!(),
157             Error::Custom { reason } => reason.clone(),
158         };
160         write!(f, "{}", err)
161     }
164 impl Error {
165     pub fn with_reason(reason: FinalError) -> Self {
166         Self::Custom { reason }
167     }
170 impl From<std::io::Error> for Error {
171     fn from(err: std::io::Error) -> Self {
172         match err.kind() {
173             std::io::ErrorKind::NotFound => panic!("{}", err),
174             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
175             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
176             _other => Self::IoError { reason: err.to_string() },
177         }
178     }
181 impl From<zip::result::ZipError> for Error {
182     fn from(err: zip::result::ZipError) -> Self {
183         use zip::result::ZipError::*;
184         match err {
185             Io(io_err) => Self::from(io_err),
186             InvalidArchive(filename) => Self::InvalidZipArchive(filename),
187             FileNotFound => Self::FileNotFound("".into()),
188             UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
189         }
190     }
193 impl From<walkdir::Error> for Error {
194     fn from(err: walkdir::Error) -> Self {
195         Self::WalkdirError { reason: err.to_string() }
196     }
199 impl From<oof::OofError> for Error {
200     fn from(err: oof::OofError) -> Self {
201         Self::OofError(err)
202     }