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