1 //! Error types definitions.
3 //! All usage errors will pass through the Error enum, a lot of them in the Error::Custom.
13 accessible::is_running_in_accessible_mode,
14 extension::{PRETTY_SUPPORTED_ALIASES, PRETTY_SUPPORTED_EXTENSIONS},
18 /// All errors that can be generated by `ouch`
19 #[derive(Debug, Clone)]
21 /// An IoError that doesn't have a dedicated error variant
22 IoError { reason: String },
23 /// From lzzzz::lz4f::Error
24 Lz4Error { reason: String },
25 /// Detected from io::Error if .kind() is io::ErrorKind::NotFound
26 NotFound { error_title: String },
27 /// NEEDS MORE CONTEXT
28 AlreadyExists { error_title: String },
29 /// From zip::result::ZipError::InvalidArchive
30 InvalidZipArchive(&'static str),
31 /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied
32 PermissionDenied { error_title: String },
33 /// From zip::result::ZipError::UnsupportedArchive
34 UnsupportedZipArchive(&'static str),
35 /// We don't support compressing the root folder.
36 CompressingRootFolder,
37 /// Specialized walkdir's io::Error wrapper with additional information on the error
38 WalkdirError { reason: String },
39 /// Custom and unique errors are reported in this variant
40 Custom { reason: FinalError },
41 /// Invalid format passed to `--format`
42 InvalidFormatFlag { text: OsString, reason: String },
43 /// From sevenz_rust::Error
44 SevenzipError { reason: String },
45 /// Recognised but unsupported format
46 // currently only RAR when built without the `unrar` feature
47 UnsupportedFormat { reason: String },
48 /// Invalid password provided
49 InvalidPassword { reason: String },
52 /// Alias to std's Result with ouch's Error
53 pub type Result<T> = std::result::Result<T, Error>;
55 /// A string either heap-allocated or located in static storage
56 pub type CowStr = Cow<'static, str>;
58 /// Pretty final error message for end users, crashing the program after display.
59 #[derive(Clone, Debug, Default, PartialEq, Eq)]
60 pub struct FinalError {
61 /// Should be made of just one line, appears after the "\[ERROR\]" part
63 /// Shown as a unnumbered list in yellow
65 /// Shown as green at the end to give hints on how to work around this error, if it's fixable
69 impl Display for FinalError {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 use crate::utils::colors::*;
75 // When in ACCESSIBLE mode, the square brackets are suppressed
76 if is_running_in_accessible_mode() {
77 write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
79 write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
83 for detail in &self.details {
84 write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
88 if !self.hints.is_empty() {
89 // Separate by one blank line.
91 // to reduce redundant output for text-to-speech systems, braille
92 // displays and so on, only print "hints" once in ACCESSIBLE mode
93 if is_running_in_accessible_mode() {
94 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
95 for hint in &self.hints {
96 write!(f, "\n{hint}")?;
99 for hint in &self.hints {
100 write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
112 pub fn with_title(title: impl Into<CowStr>) -> Self {
120 /// Add one detail line, can have multiple
122 pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
123 self.details.push(detail.into());
127 /// Add one hint line, can have multiple
129 pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
130 self.hints.push(hint.into());
134 /// Adds all supported formats as hints.
136 /// This is what it looks like:
138 /// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst
139 /// hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
141 pub fn hint_all_supported_formats(self) -> Self {
142 self.hint(format!("Supported extensions are: {}", PRETTY_SUPPORTED_EXTENSIONS))
143 .hint(format!("Supported aliases are: {}", PRETTY_SUPPORTED_ALIASES))
147 impl From<Error> for FinalError {
148 fn from(err: Error) -> Self {
150 Error::WalkdirError { reason } => FinalError::with_title(reason),
151 Error::NotFound { error_title } => FinalError::with_title(error_title).detail("File not found"),
152 Error::CompressingRootFolder => {
153 FinalError::with_title("It seems you're trying to compress the root folder.")
154 .detail("This is unadvisable since ouch does compressions in-memory.")
155 .hint("Use a more appropriate tool for this, such as rsync.")
157 Error::IoError { reason } => FinalError::with_title(reason),
158 Error::Lz4Error { reason } => FinalError::with_title(reason),
159 Error::AlreadyExists { error_title } => FinalError::with_title(error_title).detail("File already exists"),
160 Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(reason),
161 Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
162 Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(reason),
163 Error::InvalidFormatFlag { reason, text } => {
164 FinalError::with_title(format!("Failed to parse `--format {}`", os_str_to_str(&text)))
166 .hint_all_supported_formats()
169 .hint(" --format tar")
170 .hint(" --format gz")
171 .hint(" --format tar.gz")
173 Error::Custom { reason } => reason.clone(),
174 Error::SevenzipError { reason } => FinalError::with_title("7z error").detail(reason),
175 Error::UnsupportedFormat { reason } => {
176 FinalError::with_title("Recognised but unsupported format").detail(reason.clone())
178 Error::InvalidPassword { reason } => FinalError::with_title("Invalid password").detail(reason.clone()),
183 impl fmt::Display for Error {
184 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185 let err = FinalError::from(self.clone());
190 impl From<std::io::Error> for Error {
191 fn from(err: std::io::Error) -> Self {
192 let error_title = err.to_string();
195 io::ErrorKind::NotFound => Self::NotFound { error_title },
196 io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title },
197 io::ErrorKind::AlreadyExists => Self::AlreadyExists { error_title },
198 _other => Self::IoError { reason: error_title },
203 impl From<bzip3::Error> for Error {
204 fn from(err: bzip3::Error) -> Self {
205 use bzip3::Error as Bz3Error;
207 Bz3Error::Io(inner) => inner.into(),
208 Bz3Error::BlockSize | Bz3Error::ProcessBlock(_) | Bz3Error::InvalidSignature => {
209 FinalError::with_title("bzip3 error").detail(err.to_string()).into()
215 impl From<zip::result::ZipError> for Error {
216 fn from(err: zip::result::ZipError) -> Self {
217 use zip::result::ZipError;
219 ZipError::Io(io_err) => Self::from(io_err),
220 ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
221 ZipError::FileNotFound => Self::Custom {
222 reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
224 ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
229 #[cfg(feature = "unrar")]
230 impl From<unrar::error::UnrarError> for Error {
231 fn from(err: unrar::error::UnrarError) -> Self {
233 reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)),
238 impl From<sevenz_rust::Error> for Error {
239 fn from(err: sevenz_rust::Error) -> Self {
240 Self::SevenzipError {
241 reason: err.to_string(),
246 impl From<ignore::Error> for Error {
247 fn from(err: ignore::Error) -> Self {
249 reason: err.to_string(),
254 impl From<FinalError> for Error {
255 fn from(err: FinalError) -> Self {
256 Self::Custom { reason: err }