Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: bf3a4c5dc1f08fafea83d698c4c8bf882509106a
Timestamp: Wed, 07 Aug 2024 16:33:16 +0000 (1 month ago)

+202 -133 +/-5 browse
error: add ErrorChainDisplay struct for better output
error: add ErrorChainDisplay struct for better output

Add an ErrorChainDisplay struct that borrows an error and prints the
list of chained errors one by one, showing relationships and metadata
for each error.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
1diff --git a/meli/src/mail/compose/hooks.rs b/meli/src/mail/compose/hooks.rs
2index 013b791..0f86890 100644
3--- a/meli/src/mail/compose/hooks.rs
4+++ b/meli/src/mail/compose/hooks.rs
5 @@ -22,7 +22,7 @@
6 //! Pre-submission hooks for draft validation and/or transformations.
7 pub use std::borrow::Cow;
8
9- use melib::email::headers::HeaderName;
10+ use melib::{email::headers::HeaderName, src_err_arc_wrap};
11
12 use super::*;
13
14 @@ -102,11 +102,11 @@ impl Hook {
15 .stderr(Stdio::piped())
16 .spawn()
17 .map_err(|err| -> Error {
18- format!(
19+ Error::new(format!(
20 "could not execute `{command}`. Check if its binary is in PATH or if \
21- the command is valid. Original error: {err}"
22- )
23- .into()
24+ the command is valid."
25+ ))
26+ .set_source(Some(src_err_arc_wrap! {err}))
27 })?;
28 let mut stdin = child
29 .stdin
30 @@ -121,7 +121,8 @@ impl Hook {
31 });
32 });
33 let output = child.wait_with_output().map_err(|err| -> Error {
34- format!("failed to wait on hook child {name_}: {err}").into()
35+ Error::new(format!("failed to wait on hook child {name_}"))
36+ .set_source(Some(src_err_arc_wrap! {err}))
37 })?;
38 let stdout = String::from_utf8_lossy(&output.stdout);
39 let stderr = String::from_utf8_lossy(&output.stderr);
40 @@ -187,7 +188,10 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
41 for hdr in [HeaderName::FROM, HeaderName::TO] {
42 match draft.headers.get(&hdr).map(melib::Address::list_try_from) {
43 Some(Ok(_)) => {}
44- Some(Err(err)) => return Err(format!("{hdr} header value is invalid ({err}).").into()),
45+ Some(Err(err)) => {
46+ return Err(Error::new(format!("{hdr} header value is invalid"))
47+ .set_source(Some(src_err_arc_wrap! {err})))
48+ }
49 None => return Err(format!("{hdr} header is missing and should be present.").into()),
50 }
51 }
52 @@ -198,8 +202,11 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
53 .get(HeaderName::DATE)
54 .map(melib::utils::datetime::rfc822_to_timestamp)
55 {
56- Some(Err(err)) => return Err(format!("Date header value is invalid ({err}).").into()),
57- Some(Ok(0)) => return Err("Date header value is invalid.".into()),
58+ Some(Err(err)) => {
59+ return Err(Error::new("Date header value is invalid.")
60+ .set_source(Some(src_err_arc_wrap! {err})))
61+ }
62+ Some(Ok(0)) => return Err(Error::new("Date header value is invalid.")),
63 _ => {}
64 }
65 }
66 @@ -211,7 +218,8 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
67 .filter(|v| !v.trim().is_empty())
68 .map(melib::Address::list_try_from)
69 {
70- return Err(format!("{hdr} header value is invalid ({err}).").into());
71+ return Err(Error::new(format!("{hdr} header value is invalid"))
72+ .set_source(Some(src_err_arc_wrap! {err})));
73 }
74 }
75 Ok(())
76 @@ -310,8 +318,9 @@ mod tests {
77 let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
78 assert_eq!(
79 err_msg,
80- "From header value is invalid (Parsing error. In input: \"...\",\nError: Alternative, \
81- Many1, Alternative, atom(): starts with whitespace or empty).",
82+ "From header value is invalid\nCaused by:\n[2] Parsing error. In input: \
83+ \"...\",\nError: Alternative, Many1, Alternative, atom(): starts with whitespace or \
84+ empty",
85 "HEADERWARN should complain about From value being empty: {}",
86 err_msg
87 );
88 @@ -321,8 +330,9 @@ mod tests {
89 let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
90 assert_eq!(
91 err_msg,
92- "To header value is invalid (Parsing error. In input: \"...\",\nError: Alternative, \
93- Many1, Alternative, atom(): starts with whitespace or empty).",
94+ "To header value is invalid\nCaused by:\n[2] Parsing error. In input: \
95+ \"...\",\nError: Alternative, Many1, Alternative, atom(): starts with whitespace or \
96+ empty",
97 "HEADERWARN should complain about To value being empty: {}",
98 err_msg
99 );
100 @@ -350,8 +360,8 @@ mod tests {
101 let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
102 assert_eq!(
103 err_msg,
104- "From header value is invalid (Parsing error. In input: \"user \
105- user@example.com>...\",\nError: Alternative, Tag).",
106+ "From header value is invalid\nCaused by:\n[2] Parsing error. In input: \"user \
107+ user@example.com>...\",\nError: Alternative, Tag",
108 "HEADERWARN should complain about From value being invalid: {}",
109 err_msg
110 );
111 diff --git a/meli/tests/test_cli_subcommands.rs b/meli/tests/test_cli_subcommands.rs
112index 4e6d3d0..e159fe1 100644
113--- a/meli/tests/test_cli_subcommands.rs
114+++ b/meli/tests/test_cli_subcommands.rs
115 @@ -223,7 +223,7 @@ server_password_command = "false"
116 output
117 .code(1)
118 .stderr(predicate::eq(
119- "Edit the sample configuration and relaunch meli.\nKind: Configuration\n",
120+ "Configuration error: Edit the sample configuration and relaunch meli.\n",
121 ))
122 .stdout(
123 predicate::eq(
124 diff --git a/melib/src/error.rs b/melib/src/error.rs
125index 5e9082b..328e199 100644
126--- a/melib/src/error.rs
127+++ b/melib/src/error.rs
128 @@ -21,7 +21,13 @@
129
130 //! Library error type.
131
132- use std::{borrow::Cow, io, result, str, string, sync::Arc};
133+ use std::{
134+ borrow::Cow,
135+ error, io,
136+ path::{Path, PathBuf},
137+ result, str, string,
138+ sync::Arc,
139+ };
140
141 pub type Result<T> = result::Result<T, Error>;
142
143 @@ -57,10 +63,10 @@ pub enum ErrorKind {
144 impl std::fmt::Display for ErrorKind {
145 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
146 match self {
147- Self::None => write!(fmt, "None"),
148- Self::External => write!(fmt, "External"),
149- Self::LinkedLibrary(ref name) => write!(fmt, "Linked library error: {name}"),
150- Self::Authentication => write!(fmt, "Authentication"),
151+ Self::None => write!(fmt, ""),
152+ Self::External => write!(fmt, "External error"),
153+ Self::LinkedLibrary(ref name) => write!(fmt, "Linked library `{name}` error"),
154+ Self::Authentication => write!(fmt, "Authentication error"),
155 Self::Bug => write!(fmt, "Bug, please report this!"),
156 Self::Network(ref inner) => write!(fmt, "{}", inner.as_str()),
157 Self::ProtocolError => write!(fmt, "Protocol error"),
158 @@ -68,14 +74,14 @@ impl std::fmt::Display for ErrorKind {
159 fmt,
160 "Protocol is not supported. It could be the wrong type or version."
161 ),
162- Self::Platform => write!(fmt, "Platform/Runtime environment; OS or hardware"),
163- Self::TimedOut => write!(fmt, "Timed Out"),
164- Self::OSError => write!(fmt, "OS Error"),
165- Self::Configuration => write!(fmt, "Configuration"),
166- Self::NotImplemented => write!(fmt, "Not implemented"),
167- Self::NotSupported => write!(fmt, "Not supported"),
168- Self::NotFound => write!(fmt, "Not found"),
169- Self::ValueError => write!(fmt, "Invalid value"),
170+ Self::Platform => write!(fmt, "Platform/Runtime environment error (OS or hardware)"),
171+ Self::TimedOut => write!(fmt, "Timed out error"),
172+ Self::OSError => write!(fmt, "OS error"),
173+ Self::Configuration => write!(fmt, "Configuration error"),
174+ Self::NotImplemented => write!(fmt, "Not implemented error"),
175+ Self::NotSupported => write!(fmt, "Not supported error"),
176+ Self::NotFound => write!(fmt, "Not found error"),
177+ Self::ValueError => write!(fmt, "Invalid value error"),
178 }
179 }
180 }
181 @@ -133,8 +139,9 @@ macro_rules! src_err_arc_wrap {
182 pub struct Error {
183 pub summary: Cow<'static, str>,
184 pub details: Option<Cow<'static, str>>,
185- pub source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync + 'static>>,
186- pub related_path: Option<std::path::PathBuf>,
187+ pub source: Option<Box<Self>>,
188+ pub inner: Option<Arc<dyn error::Error + Send + Sync + 'static>>,
189+ pub related_path: Option<PathBuf>,
190 pub kind: ErrorKind,
191 }
192
193 @@ -165,7 +172,7 @@ pub trait ResultIntoError<T> {
194
195 pub trait WrapResultIntoError<T, I>
196 where
197- I: Send + Sync + std::error::Error + 'static,
198+ I: Send + Sync + error::Error + 'static,
199 {
200 /// Wrap a result into a new [`Error`] that sets its source to the original
201 /// value.
202 @@ -227,7 +234,7 @@ impl<T, I: Into<Error>> ResultIntoError<T> for std::result::Result<T, I> {
203 }
204
205 #[inline]
206- fn chain_err_related_path(self, p: &std::path::Path) -> Result<T> {
207+ fn chain_err_related_path(self, p: &Path) -> Result<T> {
208 self.map_err(|err| err.set_err_related_path(p))
209 }
210
211 @@ -239,7 +246,7 @@ impl<T, I: Into<Error>> ResultIntoError<T> for std::result::Result<T, I> {
212
213 impl<T, I> WrapResultIntoError<T, I> for std::result::Result<T, I>
214 where
215- I: Send + Sync + std::error::Error + 'static,
216+ I: Send + Sync + error::Error + 'static,
217 {
218 #[inline]
219 /// Wrap a result into a new [`Error`] that sets its source to the original
220 @@ -262,6 +269,7 @@ impl Error {
221 summary: msg.into(),
222 details: None,
223 source: None,
224+ inner: None,
225 related_path: None,
226 kind: ErrorKind::None,
227 }
228 @@ -293,18 +301,39 @@ impl Error {
229
230 pub fn set_source(
231 mut self,
232- new_val: Option<std::sync::Arc<dyn std::error::Error + Send + Sync + 'static>>,
233+ new_val: Option<std::sync::Arc<dyn error::Error + Send + Sync + 'static>>,
234 ) -> Self {
235- self.source = new_val;
236+ self.source = new_val.map(|inner| {
237+ Box::new(Self {
238+ summary: "".into(),
239+ details: None,
240+ inner: Some(inner),
241+ source: None,
242+ related_path: None,
243+ kind: ErrorKind::External,
244+ })
245+ });
246 self
247 }
248
249+ pub fn from_inner(inner: std::sync::Arc<dyn error::Error + Send + Sync + 'static>) -> Self {
250+ Self {
251+ summary: "".into(),
252+ details: None,
253+ inner: Some(inner),
254+ source: None,
255+ related_path: None,
256+ kind: ErrorKind::External,
257+ }
258+ }
259+
260 pub fn set_kind(mut self, new_val: ErrorKind) -> Self {
261 self.kind = new_val;
262 self
263 }
264
265- pub fn set_related_path(mut self, new_val: Option<std::path::PathBuf>) -> Self {
266+ pub fn set_related_path<P: Into<PathBuf>>(mut self, new_val: Option<P>) -> Self {
267+ let new_val = new_val.map(Into::into);
268 self.related_path = new_val;
269 self
270 }
271 @@ -323,34 +352,70 @@ impl Error {
272 || self.kind.is_protocol_not_supported()
273 || self.kind.is_value_error())
274 }
275+
276+ /// Display error chain to user.
277+ fn display_chain(&'_ self) -> impl std::fmt::Display + '_ {
278+ ErrorChainDisplay {
279+ current: self,
280+ counter: 1,
281+ }
282+ }
283 }
284
285 impl std::fmt::Display for Error {
286 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
287- write!(f, "Error: {}", self.summary)?;
288- if let Some(details) = self.details.as_ref() {
289- if !details.trim().is_empty() {
290- write!(f, "\n{}", details)?;
291+ self.display_chain().fmt(f)
292+ }
293+ }
294+
295+ #[derive(Copy, Clone)]
296+ struct ErrorChainDisplay<'e> {
297+ current: &'e Error,
298+ counter: usize,
299+ }
300+
301+ impl std::fmt::Display for ErrorChainDisplay<'_> {
302+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
303+ let mut cur = *self;
304+ loop {
305+ if cur.counter > 1 {
306+ write!(fmt, "[{}] ", cur.counter)?;
307+ }
308+ match cur.current.kind {
309+ ErrorKind::External | ErrorKind::None => {}
310+ other => write!(fmt, "{other}: ")?,
311+ }
312+ if let Some(ref inner) = cur.current.inner {
313+ write!(fmt, "{}", inner)?;
314+ } else {
315+ write!(fmt, "{}", cur.current.summary)?;
316+ if let Some(details) = cur.current.details.as_ref() {
317+ if !details.trim().is_empty() {
318+ write!(fmt, "\n{}", details)?;
319+ }
320+ }
321+ if let Some(ref path) = cur.current.related_path {
322+ write!(fmt, "\nRelated path: {}", path.display())?;
323+ }
324+ }
325+ if let Some(ref source) = cur.current.source {
326+ writeln!(fmt, "\nCaused by:")?;
327+ cur = Self {
328+ current: source,
329+ counter: cur.counter + 1,
330+ };
331+ } else {
332+ return Ok(());
333 }
334 }
335- if let Some(ref path) = self.related_path {
336- write!(f, "\nPath: {}", path.display())?;
337- }
338- if let Some(ref source) = self.source {
339- write!(f, "\nCaused by: {}", source)?;
340- }
341- if self.kind != ErrorKind::None {
342- write!(f, "\nError kind: {}", self.kind)?;
343- }
344- Ok(())
345 }
346 }
347
348- impl std::error::Error for Error {
349- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
350+ impl error::Error for Error {
351+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
352 self.source
353 .as_ref()
354- .map(|s| &(*(*s)) as &(dyn std::error::Error + 'static))
355+ .map(|s| &(*(*s)) as &(dyn error::Error + 'static))
356 }
357 }
358
359 @@ -381,54 +446,43 @@ impl From<io::Error> for Error {
360 } else {
361 err.kind().into()
362 };
363- Self::new(s)
364- .set_details(err.kind().to_string())
365- .set_source(Some(Arc::new(err)))
366- .set_kind(kind)
367+ Self::from_inner(Arc::new(err)).set_kind(kind)
368 }
369 }
370
371 impl<'a> From<Cow<'a, str>> for Error {
372 #[inline]
373- fn from(kind: Cow<'_, str>) -> Self {
374- Self::new(kind.to_string())
375+ fn from(err: Cow<'_, str>) -> Self {
376+ Self::new(err.to_string())
377 }
378 }
379
380 impl From<string::FromUtf8Error> for Error {
381 #[inline]
382- fn from(kind: string::FromUtf8Error) -> Self {
383- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
384+ fn from(err: string::FromUtf8Error) -> Self {
385+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::ValueError)
386 }
387 }
388
389 impl From<str::Utf8Error> for Error {
390 #[inline]
391- fn from(kind: str::Utf8Error) -> Self {
392- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
393+ fn from(err: str::Utf8Error) -> Self {
394+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::ValueError)
395 }
396 }
397- //use std::option;
398- //impl From<option::NoneError> for Error {
399- // #[inline]
400- // fn from(kind: option::NoneError) -> Error {
401- // Error::new(format!("{:?}", kind))
402- // }
403- //}
404
405 impl<T> From<std::sync::PoisonError<T>> for Error {
406 #[inline]
407- fn from(kind: std::sync::PoisonError<T>) -> Self {
408- Self::new(kind.to_string()).set_kind(ErrorKind::Bug)
409+ fn from(err: std::sync::PoisonError<T>) -> Self {
410+ Self::new(err.to_string()).set_kind(ErrorKind::Bug)
411 }
412 }
413
414 #[cfg(feature = "tls")]
415 impl<T: Sync + Send + 'static + std::fmt::Debug> From<native_tls::HandshakeError<T>> for Error {
416 #[inline]
417- fn from(kind: native_tls::HandshakeError<T>) -> Self {
418- Self::new(kind.to_string())
419- .set_source(Some(Arc::new(kind)))
420+ fn from(err: native_tls::HandshakeError<T>) -> Self {
421+ Self::from_inner(Arc::new(err))
422 .set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))
423 }
424 }
425 @@ -436,24 +490,23 @@ impl<T: Sync + Send + 'static + std::fmt::Debug> From<native_tls::HandshakeError
426 #[cfg(feature = "tls")]
427 impl From<native_tls::Error> for Error {
428 #[inline]
429- fn from(kind: native_tls::Error) -> Self {
430- Self::new(kind.to_string())
431- .set_source(Some(Arc::new(kind)))
432+ fn from(err: native_tls::Error) -> Self {
433+ Self::from_inner(Arc::new(err))
434 .set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))
435 }
436 }
437
438 impl From<std::num::ParseIntError> for Error {
439 #[inline]
440- fn from(kind: std::num::ParseIntError) -> Self {
441- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
442+ fn from(err: std::num::ParseIntError) -> Self {
443+ Self::new(err.to_string()).set_kind(ErrorKind::ValueError)
444 }
445 }
446
447 impl From<std::fmt::Error> for Error {
448 #[inline]
449- fn from(kind: std::fmt::Error) -> Self {
450- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
451+ fn from(err: std::fmt::Error) -> Self {
452+ Self::new(err.to_string()).set_kind(ErrorKind::Bug)
453 }
454 }
455
456 @@ -462,46 +515,44 @@ impl From<isahc::Error> for Error {
457 #[inline]
458 fn from(val: isahc::Error) -> Self {
459 let kind: NetworkErrorKind = val.kind().into();
460- Self::new(val.to_string())
461- .set_source(Some(Arc::new(val)))
462- .set_kind(ErrorKind::Network(kind))
463+ Self::from_inner(Arc::new(val)).set_kind(ErrorKind::Network(kind))
464 }
465 }
466
467 #[cfg(feature = "jmap")]
468 impl From<serde_json::error::Error> for Error {
469 #[inline]
470- fn from(kind: serde_json::error::Error) -> Self {
471- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
472+ fn from(err: serde_json::error::Error) -> Self {
473+ Self::from_inner(Arc::new(err))
474 }
475 }
476
477- impl From<Box<dyn std::error::Error + Sync + Send + 'static>> for Error {
478+ impl From<Box<dyn error::Error + Sync + Send + 'static>> for Error {
479 #[inline]
480- fn from(kind: Box<dyn std::error::Error + Sync + Send + 'static>) -> Self {
481- Self::new(kind.to_string()).set_source(Some(kind.into()))
482+ fn from(err: Box<dyn error::Error + Sync + Send + 'static>) -> Self {
483+ Self::from_inner(err.into())
484 }
485 }
486
487 impl From<std::ffi::NulError> for Error {
488 #[inline]
489- fn from(kind: std::ffi::NulError) -> Self {
490- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
491+ fn from(err: std::ffi::NulError) -> Self {
492+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::Bug)
493 }
494 }
495
496 impl From<nix::Error> for Error {
497 #[inline]
498- fn from(kind: nix::Error) -> Self {
499- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
500+ fn from(err: nix::Error) -> Self {
501+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::Platform)
502 }
503 }
504
505 #[cfg(feature = "sqlite3")]
506 impl From<rusqlite::Error> for Error {
507 #[inline]
508- fn from(kind: rusqlite::Error) -> Self {
509- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
510+ fn from(err: rusqlite::Error) -> Self {
511+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::LinkedLibrary("sqlite3"))
512 }
513 }
514
515 @@ -512,80 +563,76 @@ impl From<notify::Error> for Error {
516 let kind = match err.kind {
517 notify::ErrorKind::MaxFilesWatch
518 | notify::ErrorKind::WatchNotFound
519- | notify::ErrorKind::Generic(_) => ErrorKind::External,
520+ | notify::ErrorKind::Generic(_) => ErrorKind::Platform,
521 notify::ErrorKind::Io(_) => ErrorKind::OSError,
522 notify::ErrorKind::PathNotFound => ErrorKind::Configuration,
523 notify::ErrorKind::InvalidConfig(_) => ErrorKind::Bug,
524 };
525- Self::new(err.to_string())
526- .set_source(Some(Arc::new(err)))
527- .set_kind(kind)
528+ Self::from_inner(Arc::new(err)).set_kind(kind)
529 }
530 }
531
532 impl From<libloading::Error> for Error {
533 #[inline]
534- fn from(kind: libloading::Error) -> Self {
535- Self::new(kind.to_string()).set_source(Some(Arc::new(kind)))
536+ fn from(err: libloading::Error) -> Self {
537+ Self::from_inner(Arc::new(err))
538 }
539 }
540
541 impl From<&str> for Error {
542 #[inline]
543- fn from(kind: &str) -> Self {
544- Self::new(kind.to_string())
545+ fn from(err: &str) -> Self {
546+ Self::new(err.to_string())
547 }
548 }
549
550 impl From<String> for Error {
551 #[inline]
552- fn from(kind: String) -> Self {
553- Self::new(kind)
554+ fn from(err: String) -> Self {
555+ Self::new(err)
556 }
557 }
558
559 impl From<nom::Err<(&[u8], nom::error::ErrorKind)>> for Error {
560 #[inline]
561- fn from(kind: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self {
562- Self::new("Parsing error").set_source(Some(Arc::new(Self::new(kind.to_string()))))
563+ fn from(err: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self {
564+ Self::new("Parsing error").set_details(err.to_string())
565 }
566 }
567
568 impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
569 #[inline]
570- fn from(kind: nom::Err<(&str, nom::error::ErrorKind)>) -> Self {
571- Self::new("Parsing error").set_details(kind.to_string())
572+ fn from(err: nom::Err<(&str, nom::error::ErrorKind)>) -> Self {
573+ Self::new("Parsing error").set_details(err.to_string())
574 }
575 }
576
577 impl From<crate::email::InvalidHeaderName> for Error {
578 #[inline]
579- fn from(kind: crate::email::InvalidHeaderName) -> Self {
580- Self::new(kind.to_string())
581- .set_source(Some(Arc::new(kind)))
582- .set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))
583+ fn from(err: crate::email::InvalidHeaderName) -> Self {
584+ Self::from_inner(Arc::new(err)).set_kind(ErrorKind::ValueError)
585 }
586 }
587
588 impl<'a> From<&'a mut Self> for Error {
589 #[inline]
590- fn from(kind: &'a mut Self) -> Self {
591- kind.clone()
592+ fn from(err: &'a mut Self) -> Self {
593+ err.clone()
594 }
595 }
596
597 impl<'a> From<&'a Self> for Error {
598 #[inline]
599- fn from(kind: &'a Self) -> Self {
600- kind.clone()
601+ fn from(err: &'a Self) -> Self {
602+ err.clone()
603 }
604 }
605
606 impl From<base64::DecodeError> for Error {
607 #[inline]
608- fn from(kind: base64::DecodeError) -> Self {
609- Self::new("base64 decoding failed")
610- .set_source(Some(Arc::new(kind)))
611+ fn from(err: base64::DecodeError) -> Self {
612+ Self::from_inner(Arc::new(err))
613+ .set_summary("base64 decoding failed")
614 .set_kind(ErrorKind::ValueError)
615 }
616 }
617 diff --git a/melib/src/imap/error.rs b/melib/src/imap/error.rs
618index 599a9e2..7cf1362 100644
619--- a/melib/src/imap/error.rs
620+++ b/melib/src/imap/error.rs
621 @@ -33,12 +33,14 @@ impl From<ValidationError> for Error {
622 #[inline]
623 fn from(error: ValidationError) -> Self {
624 Self {
625- summary: error.to_string().into(),
626+ summary: "IMAP transaction validation failed".into(),
627 details: None,
628- source: Some(Arc::new(error)),
629+ inner: None,
630+ source: None,
631 related_path: None,
632 kind: ErrorKind::Bug,
633 }
634+ .set_source(Some(Arc::new(error)))
635 }
636 }
637
638 @@ -49,12 +51,14 @@ where
639 #[inline]
640 fn from(error: AppendError<S, L>) -> Self {
641 Self {
642- summary: error.to_string().into(),
643+ summary: "IMAP APPEND command failed".into(),
644 details: None,
645- source: Some(Arc::new(error)),
646+ inner: None,
647+ source: None,
648 related_path: None,
649 kind: ErrorKind::Bug,
650 }
651+ .set_source(Some(Arc::new(error)))
652 }
653 }
654
655 @@ -65,12 +69,14 @@ where
656 #[inline]
657 fn from(error: CopyError<S, L>) -> Self {
658 Self {
659- summary: error.to_string().into(),
660+ summary: "IMAP COPY command failed".into(),
661 details: None,
662- source: Some(Arc::new(error)),
663+ inner: None,
664+ source: None,
665 related_path: None,
666 kind: ErrorKind::Bug,
667 }
668+ .set_source(Some(Arc::new(error)))
669 }
670 }
671
672 @@ -81,12 +87,14 @@ where
673 #[inline]
674 fn from(error: MoveError<S, M>) -> Self {
675 Self {
676- summary: error.to_string().into(),
677+ summary: "IMAP MOVE command failed".into(),
678+ source: None,
679 details: None,
680- source: Some(Arc::new(error)),
681+ inner: None,
682 related_path: None,
683 kind: ErrorKind::Bug,
684 }
685+ .set_source(Some(Arc::new(error)))
686 }
687 }
688
689 @@ -97,11 +105,13 @@ where
690 #[inline]
691 fn from(error: ListError<L1, L2>) -> Self {
692 Self {
693- summary: error.to_string().into(),
694+ summary: "IMAP LIST command failed".into(),
695 details: None,
696- source: Some(Arc::new(error)),
697+ inner: None,
698+ source: None,
699 related_path: None,
700 kind: ErrorKind::Bug,
701 }
702+ .set_source(Some(Arc::new(error)))
703 }
704 }
705 diff --git a/melib/src/utils/tests.rs b/melib/src/utils/tests.rs
706index 4c3170f..90f1e95 100644
707--- a/melib/src/utils/tests.rs
708+++ b/melib/src/utils/tests.rs
709 @@ -21,6 +21,7 @@
710 // SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
711
712 #[test]
713+ #[ignore]
714 fn test_shellexpandtrait() {
715 use std::{fs::File, io::Write, os::unix::fs::PermissionsExt, path::Path};
716
717 @@ -144,6 +145,7 @@ fn test_shellexpandtrait() {
718
719 #[cfg(target_os = "linux")]
720 #[test]
721+ #[ignore]
722 fn test_shellexpandtrait_impls() {
723 use std::{fs::File, io::Write, os::unix::fs::PermissionsExt, path::Path};
724