Merge pull request #215 from figsoda/snappy
[ouch.git] / src / error.rs
blobb8a7ddbe01f4eff25a1a341e092500bebd6bf7b5
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::fmt::{self, Display};
7 use crate::utils::colors::*;
9 #[allow(missing_docs)]
10 /// All errors that can be generated by `ouch`
11 #[derive(Debug, PartialEq)]
12 pub enum Error {
13     /// Not every IoError, some of them get filtered by `From<io::Error>` into other variants
14     IoError { reason: String },
15     /// From lzzzz::lz4f::Error
16     Lz4Error { reason: String },
17     /// Detected from io::Error if .kind() is io::ErrorKind::NotFound
18     NotFound { error_title: String },
19     /// NEEDS MORE CONTEXT
20     AlreadyExists { error_title: String },
21     /// From zip::result::ZipError::InvalidArchive
22     InvalidZipArchive(&'static str),
23     /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
24     PermissionDenied { error_title: String },
25     /// From zip::result::ZipError::UnsupportedArchive
26     UnsupportedZipArchive(&'static str),
27     /// TO BE REMOVED
28     CompressingRootFolder,
29     /// Specialized walkdir's io::Error wrapper with additional information on the error
30     WalkdirError { reason: String },
31     /// Custom and unique errors are reported in this variant
32     Custom { reason: FinalError },
35 /// Alias to std's Result with ouch's Error
36 pub type Result<T> = std::result::Result<T, Error>;
38 /// Pretty final error message for end users, crashing the program after display.
39 #[derive(Clone, Debug, Default, PartialEq)]
40 pub struct FinalError {
41     /// Should be made of just one line, appears after the "\[ERROR\]" part
42     title: String,
43     /// Shown as a unnumbered list in yellow
44     details: Vec<String>,
45     /// Shown as green at the end to give hints on how to work around this error, if it's fixable
46     hints: Vec<String>,
49 impl Display for FinalError {
50     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51         // Title
52         //
53         // When in ACCESSIBLE mode, the square brackets are suppressed
54         if *crate::cli::ACCESSIBLE.get().unwrap_or(&false) {
55             write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
56         } else {
57             write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
58         }
60         // Details
61         for detail in &self.details {
62             write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
63         }
65         // Hints
66         if !self.hints.is_empty() {
67             // Separate by one blank line.
68             writeln!(f)?;
69             // to reduce redundant output for text-to-speach systems, braille
70             // displays and so on, only print "hints" once in ACCESSIBLE mode
71             if *crate::cli::ACCESSIBLE.get().unwrap_or(&false) {
72                 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
73                 for hint in &self.hints {
74                     write!(f, "\n{}", hint)?;
75                 }
76             } else {
77                 for hint in &self.hints {
78                     write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
79                 }
80             }
81         }
83         Ok(())
84     }
87 impl FinalError {
88     /// Only constructor
89     pub fn with_title(title: impl ToString) -> Self {
90         Self { title: title.to_string(), details: vec![], hints: vec![] }
91     }
93     /// Add one detail line, can have multiple
94     pub fn detail(mut self, detail: impl ToString) -> Self {
95         self.details.push(detail.to_string());
96         self
97     }
99     /// Add one hint line, can have multiple
100     pub fn hint(mut self, hint: impl ToString) -> Self {
101         self.hints.push(hint.to_string());
102         self
103     }
106 impl fmt::Display for Error {
107     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108         let err = match self {
109             Error::WalkdirError { reason } => FinalError::with_title(reason),
110             Error::NotFound { error_title } => FinalError::with_title(error_title).detail("File not found"),
111             Error::CompressingRootFolder => {
112                 FinalError::with_title("It seems you're trying to compress the root folder.")
113                     .detail("This is unadvisable since ouch does compressions in-memory.")
114                     .hint("Use a more appropriate tool for this, such as rsync.")
115             }
116             Error::IoError { reason } => FinalError::with_title(reason),
117             Error::Lz4Error { reason } => FinalError::with_title(reason),
118             Error::AlreadyExists { error_title } => FinalError::with_title(error_title).detail("File already exists"),
119             Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(reason),
120             Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
121             Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(reason),
122             Error::Custom { reason } => reason.clone(),
123         };
125         write!(f, "{}", err)
126     }
129 impl From<std::io::Error> for Error {
130     fn from(err: std::io::Error) -> Self {
131         match err.kind() {
132             std::io::ErrorKind::NotFound => Self::NotFound { error_title: err.to_string() },
133             std::io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title: err.to_string() },
134             std::io::ErrorKind::AlreadyExists => Self::AlreadyExists { error_title: err.to_string() },
135             _other => Self::IoError { reason: err.to_string() },
136         }
137     }
140 impl From<lzzzz::lz4f::Error> for Error {
141     fn from(err: lzzzz::lz4f::Error) -> Self {
142         Self::Lz4Error { reason: err.to_string() }
143     }
146 impl From<zip::result::ZipError> for Error {
147     fn from(err: zip::result::ZipError) -> Self {
148         use zip::result::ZipError;
149         match err {
150             ZipError::Io(io_err) => Self::from(io_err),
151             ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
152             ZipError::FileNotFound => {
153                 Self::Custom {
154                     reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
155                 }
156             }
157             ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
158         }
159     }
162 impl From<walkdir::Error> for Error {
163     fn from(err: walkdir::Error) -> Self {
164         Self::WalkdirError { reason: err.to_string() }
165     }
168 impl From<FinalError> for Error {
169     fn from(err: FinalError) -> Self {
170         Self::Custom { reason: err }
171     }