1 //! Error types definitions.
3 //! All usage errors will pass through the Error enum, a lot of them in the Error::Custom.
10 use crate::{accessible::is_running_in_accessible_mode, utils::colors::*};
12 /// All errors that can be generated by `ouch`
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),
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 },
37 /// From sevenz_rust::Error
38 SevenzipError(sevenz_rust::Error),
39 /// Recognised but unsupported format
40 // currently only RAR when built without the `unrar` feature
41 UnsupportedFormat { reason: String },
44 /// Alias to std's Result with ouch's Error
45 pub type Result<T> = std::result::Result<T, Error>;
47 /// A string either heap-allocated or located in static storage
48 pub type CowStr = Cow<'static, str>;
50 /// Pretty final error message for end users, crashing the program after display.
51 #[derive(Clone, Debug, Default, PartialEq, Eq)]
52 pub struct FinalError {
53 /// Should be made of just one line, appears after the "\[ERROR\]" part
55 /// Shown as a unnumbered list in yellow
57 /// Shown as green at the end to give hints on how to work around this error, if it's fixable
61 impl Display for FinalError {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 // When in ACCESSIBLE mode, the square brackets are suppressed
66 if is_running_in_accessible_mode() {
67 write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
69 write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
73 for detail in &self.details {
74 write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
78 if !self.hints.is_empty() {
79 // Separate by one blank line.
81 // to reduce redundant output for text-to-speech systems, braille
82 // displays and so on, only print "hints" once in ACCESSIBLE mode
83 if is_running_in_accessible_mode() {
84 write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
85 for hint in &self.hints {
86 write!(f, "\n{hint}")?;
89 for hint in &self.hints {
90 write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
102 pub fn with_title(title: impl Into<CowStr>) -> Self {
110 /// Add one detail line, can have multiple
112 pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
113 self.details.push(detail.into());
117 /// Add one hint line, can have multiple
119 pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
120 self.hints.push(hint.into());
125 impl fmt::Display for Error {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 let err = match self {
128 Error::WalkdirError { reason } => FinalError::with_title(reason.to_string()),
129 Error::NotFound { error_title } => FinalError::with_title(error_title.to_string()).detail("File not found"),
130 Error::CompressingRootFolder => {
131 FinalError::with_title("It seems you're trying to compress the root folder.")
132 .detail("This is unadvisable since ouch does compressions in-memory.")
133 .hint("Use a more appropriate tool for this, such as rsync.")
135 Error::IoError { reason } => FinalError::with_title(reason.to_string()),
136 Error::Lz4Error { reason } => FinalError::with_title(reason.to_string()),
137 Error::AlreadyExists { error_title } => {
138 FinalError::with_title(error_title.to_string()).detail("File already exists")
140 Error::InvalidZipArchive(reason) => FinalError::with_title("Invalid zip archive").detail(*reason),
141 Error::PermissionDenied { error_title } => {
142 FinalError::with_title(error_title.to_string()).detail("Permission denied")
144 Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(*reason),
145 Error::InvalidFormat { reason } => FinalError::with_title("Invalid archive format").detail(reason.clone()),
146 Error::Custom { reason } => reason.clone(),
147 Error::SevenzipError(reason) => FinalError::with_title("7z error").detail(reason.to_string()),
148 Error::UnsupportedFormat { reason } => {
149 FinalError::with_title("Recognised but unsupported format").detail(reason.clone())
157 impl From<std::io::Error> for Error {
158 fn from(err: std::io::Error) -> Self {
160 std::io::ErrorKind::NotFound => Self::NotFound {
161 error_title: err.to_string(),
163 std::io::ErrorKind::PermissionDenied => Self::PermissionDenied {
164 error_title: err.to_string(),
166 std::io::ErrorKind::AlreadyExists => Self::AlreadyExists {
167 error_title: err.to_string(),
169 _other => Self::IoError {
170 reason: err.to_string(),
176 impl From<zip::result::ZipError> for Error {
177 fn from(err: zip::result::ZipError) -> Self {
178 use zip::result::ZipError;
180 ZipError::Io(io_err) => Self::from(io_err),
181 ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
182 ZipError::FileNotFound => Self::Custom {
183 reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
185 ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
190 #[cfg(feature = "unrar")]
191 impl From<unrar::error::UnrarError> for Error {
192 fn from(err: unrar::error::UnrarError) -> Self {
194 reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)),
199 impl From<sevenz_rust::Error> for Error {
200 fn from(err: sevenz_rust::Error) -> Self {
201 Self::SevenzipError(err)
205 impl From<ignore::Error> for Error {
206 fn from(err: ignore::Error) -> Self {
208 reason: err.to_string(),
213 impl From<FinalError> for Error {
214 fn from(err: FinalError) -> Self {
215 Self::Custom { reason: err }