Rework `FinalError::display` into a `FinalError::Display` impl
[ouch.git] / src / error.rs
blobba7f7f236cad93ee61e053ded1ba41470a54a534
1 use std::{
2     fmt::{self, Display},
3     path::{Path, PathBuf},
4 };
6 use crate::{oof, utils::colors::*};
8 #[derive(Debug, PartialEq)]
9 pub enum Error {
10     UnknownExtensionError(String),
11     MissingExtensionError(PathBuf),
12     // TODO: get rid of this error variant
13     InvalidUnicode,
14     InvalidInput,
15     IoError { reason: String },
16     FileNotFound(PathBuf),
17     AlreadyExists,
18     InvalidZipArchive(&'static str),
19     PermissionDenied,
20     UnsupportedZipArchive(&'static str),
21     InternalError,
22     OofError(oof::OofError),
23     CompressingRootFolder,
24     MissingArgumentsForCompression,
25     CompressionTypo,
26     WalkdirError { reason: String },
29 pub type Result<T> = std::result::Result<T, Error>;
31 #[derive(Default)]
32 pub struct FinalError {
33     title: String,
34     details: Vec<String>,
35     hints: Vec<String>,
38 impl Display for FinalError {
39     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40         // Title
41         writeln!(f, "{}[ERROR]{} {}", red(), reset(), self.title)?;
43         // Details
44         for detail in &self.details {
45             writeln!(f, " {}-{} {}", white(), yellow(), detail)?;
46         }
48         // Hints
49         if !self.hints.is_empty() {
50             // Separate by one blank line.
51             writeln!(f)?;
52             for hint in &self.hints {
53                 writeln!(f, "{}hint:{} {}", green(), reset(), hint)?;
54             }
55         }
57         write!(f, "{}", reset())
58     }
61 impl FinalError {
62     pub fn with_title(title: impl ToString) -> Self {
63         Self { title: title.to_string(), details: vec![], hints: vec![] }
64     }
66     pub fn detail(&mut self, detail: impl ToString) -> &mut Self {
67         self.details.push(detail.to_string());
68         self
69     }
71     pub fn hint(&mut self, hint: impl ToString) -> &mut Self {
72         self.hints.push(hint.to_string());
73         self
74     }
76     pub fn to_owned(&mut self) -> Self {
77         std::mem::take(self)
78     }
80     pub fn display_and_crash(&self) -> ! {
81         eprintln!("{}", self);
82         std::process::exit(crate::EXIT_FAILURE)
83     }
86 impl fmt::Display for Error {
87     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88         let err = match self {
89             Error::MissingExtensionError(filename) => {
90                 let error = FinalError::with_title(format!("Cannot compress to {:?}", filename))
91                     .detail("Ouch could not detect the compression format")
92                     .hint("Use a supported format extension, like '.zip' or '.tar.gz'")
93                     .hint("Check https://github.com/vrmiguel/ouch for a full list of supported formats")
94                     .to_owned();
96                 error
97             }
98             Error::WalkdirError { reason } => FinalError::with_title(reason),
99             Error::FileNotFound(file) => {
100                 let error = if file == Path::new("") {
101                     FinalError::with_title("file not found!")
102                 } else {
103                     FinalError::with_title(format!("file {:?} not found!", file))
104                 };
106                 error
107             }
108             Error::CompressingRootFolder => {
109                 let error = FinalError::with_title("It seems you're trying to compress the root folder.")
110                     .detail("This is unadvisable since ouch does compressions in-memory.")
111                     .hint("Use a more appropriate tool for this, such as rsync.")
112                     .to_owned();
114                 error
115             }
116             Error::MissingArgumentsForCompression => {
117                 let error = FinalError::with_title("Could not compress")
118                     .detail("The compress command requires at least 2 arguments")
119                     .hint("You must provide:")
120                     .hint("  - At least one input argument.")
121                     .hint("  - The output argument.")
122                     .hint("")
123                     .hint("Example: `ouch compress image.png img.zip`")
124                     .to_owned();
126                 error
127             }
128             Error::InternalError => {
129                 let error = FinalError::with_title("InternalError :(")
130                     .detail("This should not have happened")
131                     .detail("It's probably our fault")
132                     .detail("Please help us improve by reporting the issue at:")
133                     .detail(format!("    {}https://github.com/vrmiguel/ouch/issues ", cyan()))
134                     .to_owned();
136                 error
137             }
138             Error::OofError(err) => FinalError::with_title(err),
139             Error::IoError { reason } => FinalError::with_title(reason),
140             Error::CompressionTypo => FinalError::with_title("Possible typo detected")
141                 .hint(format!("Did you mean '{}ouch compress{}'?", magenta(), reset()))
142                 .to_owned(),
143             _err => {
144                 todo!();
145             }
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 => panic!("{}", err),
156             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
157             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
158             _other => Self::IoError { reason: err.to_string() },
159         }
160     }
163 impl From<zip::result::ZipError> for Error {
164     fn from(err: zip::result::ZipError) -> Self {
165         use zip::result::ZipError::*;
166         match err {
167             Io(io_err) => Self::from(io_err),
168             InvalidArchive(filename) => Self::InvalidZipArchive(filename),
169             FileNotFound => Self::FileNotFound("".into()),
170             UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
171         }
172     }
175 impl From<walkdir::Error> for Error {
176     fn from(err: walkdir::Error) -> Self {
177         Self::WalkdirError { reason: err.to_string() }
178     }
181 impl From<oof::OofError> for Error {
182     fn from(err: oof::OofError) -> Self {
183         Self::OofError(err)
184     }