dialogs: use `Cow<str>` to avoid cloning a String
[ouch.git] / src / error.rs
blob0b0c6e307c3dac665df6683a33fb666368a4bddb
1 use std::{
2     fmt,
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 pub struct FinalError {
32     title: String,
33     details: Vec<String>,
34     hints: Vec<String>,
37 impl FinalError {
38     pub fn with_title(title: impl ToString) -> Self {
39         Self { title: title.to_string(), details: vec![], hints: vec![] }
40     }
42     pub fn detail(&mut self, detail: impl ToString) -> &mut Self {
43         self.details.push(detail.to_string());
44         self
45     }
47     pub fn hint(&mut self, hint: impl ToString) -> &mut Self {
48         self.hints.push(hint.to_string());
49         self
50     }
52     pub fn display(&self) {
53         // Title
54         eprintln!("{}[ERROR]{} {}", red(), reset(), self.title);
56         // Details
57         for detail in &self.details {
58             eprintln!(" {}-{} {}", white(), yellow(), detail);
59         }
61         // Hints
62         if !self.hints.is_empty() {
63             // Separate by one blank line.
64             eprintln!();
65             for hint in &self.hints {
66                 eprintln!("{}hint:{} {}", green(), reset(), hint);
67             }
68         }
70         // Make sure to fix colors
71         eprint!("{}", reset());
72     }
74     pub fn display_and_crash(&self) -> ! {
75         self.display();
76         std::process::exit(crate::EXIT_FAILURE)
77     }
80 impl fmt::Display for Error {
81     fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
82         match self {
83             Error::MissingExtensionError(filename) => {
84                 FinalError::with_title(format!("Cannot compress to {:?}", filename))
85                     .detail("Ouch could not detect the compression format")
86                     .hint("Use a supported format extension, like '.zip' or '.tar.gz'")
87                     .hint("Check https://github.com/vrmiguel/ouch for a full list of supported formats")
88                     .display();
89             }
90             Error::WalkdirError { reason } => {
91                 FinalError::with_title(reason).display();
92             }
93             Error::FileNotFound(file) => {
94                 if file == Path::new("") {
95                     FinalError::with_title("file not found!")
96                 } else {
97                     FinalError::with_title(format!("file {:?} not found!", file))
98                 }
99                 .display();
100             }
101             Error::CompressingRootFolder => {
102                 FinalError::with_title("It seems you're trying to compress the root folder.")
103                     .detail("This is unadvisable since ouch does compressions in-memory.")
104                     .hint("Use a more appropriate tool for this, such as rsync.")
105                     .display();
106             }
107             Error::MissingArgumentsForCompression => {
108                 FinalError::with_title("Could not compress")
109                     .detail("The compress command requires at least 2 arguments")
110                     .hint("You must provide:")
111                     .hint("  - At least one input argument.")
112                     .hint("  - The output argument.")
113                     .hint("")
114                     .hint("Example: `ouch compress image.png img.zip`")
115                     .display();
116             }
117             Error::InternalError => {
118                 FinalError::with_title("InternalError :(")
119                     .detail("This should not have happened")
120                     .detail("It's probably our fault")
121                     .detail("Please help us improve by reporting the issue at:")
122                     .detail(format!("    {}https://github.com/vrmiguel/ouch/issues ", cyan()))
123                     .display();
124             }
125             Error::OofError(err) => {
126                 FinalError::with_title(err).display();
127             }
128             Error::IoError { reason } => {
129                 FinalError::with_title(reason).display();
130             }
131             Error::CompressionTypo => {
132                 FinalError::with_title("Possible typo detected")
133                     .hint(format!("Did you mean '{}ouch compress{}'?", magenta(), reset()))
134                     .display();
135             }
136             _err => {
137                 todo!();
138             }
139         }
140         Ok(())
141     }
144 impl From<std::io::Error> for Error {
145     fn from(err: std::io::Error) -> Self {
146         match err.kind() {
147             std::io::ErrorKind::NotFound => panic!("{}", err),
148             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
149             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
150             _other => Self::IoError { reason: err.to_string() },
151         }
152     }
155 impl From<zip::result::ZipError> for Error {
156     fn from(err: zip::result::ZipError) -> Self {
157         use zip::result::ZipError::*;
158         match err {
159             Io(io_err) => Self::from(io_err),
160             InvalidArchive(filename) => Self::InvalidZipArchive(filename),
161             FileNotFound => Self::FileNotFound("".into()),
162             UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
163         }
164     }
167 impl From<walkdir::Error> for Error {
168     fn from(err: walkdir::Error) -> Self {
169         Self::WalkdirError { reason: err.to_string() }
170     }
173 impl From<oof::OofError> for Error {
174     fn from(err: oof::OofError) -> Self {
175         Self::OofError(err)
176     }