1use std::fmt;
16use std::io;
17
18pub struct Error {
20 message: String,
21 source: Option<anyhow::Error>,
22 context: Vec<(&'static str, String)>,
23}
24
25impl fmt::Display for Error {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "{}", self.message)?;
28
29 if !self.context.is_empty() {
30 write!(f, ", context: {{ ")?;
31 write!(
32 f,
33 "{}",
34 self.context
35 .iter()
36 .map(|(k, v)| format!("{k}: {v}"))
37 .collect::<Vec<_>>()
38 .join(", ")
39 )?;
40 write!(f, " }}")?;
41 }
42
43 if let Some(source) = &self.source {
44 write!(f, ", source: {source}")?;
45 }
46
47 Ok(())
48 }
49}
50
51impl fmt::Debug for Error {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 if f.alternate() {
55 let mut de = f.debug_struct("Error");
56 de.field("message", &self.message);
57 de.field("context", &self.context);
58 de.field("sources", &self.source);
59 return de.finish();
60 }
61
62 write!(f, "{}", self.message)?;
63 writeln!(f)?;
64
65 if !self.context.is_empty() {
66 writeln!(f)?;
67 writeln!(f, "Context:")?;
68 for (k, v) in self.context.iter() {
69 writeln!(f, " {k}: {v}")?;
70 }
71 }
72
73 if let Some(source) = &self.source {
74 writeln!(f)?;
75 writeln!(f, "Source:")?;
76 writeln!(f, " {source:#}")?;
77 }
78
79 Ok(())
80 }
81}
82
83impl std::error::Error for Error {
84 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85 self.source.as_ref().map(|v| v.as_ref())
86 }
87}
88
89impl Error {
90 pub fn new(message: impl Into<String>) -> Self {
92 Self {
93 message: message.into(),
94 source: None,
95 context: vec![],
96 }
97 }
98
99 pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
101 self.context.push((key, value.to_string()));
102 self
103 }
104
105 pub fn with_source(mut self, src: impl Into<anyhow::Error>) -> Self {
111 assert!(self.source.is_none(), "the source error has been set");
112
113 self.source = Some(src.into());
114 self
115 }
116
117 pub fn sources(&self) -> impl ExactSizeIterator<Item = &(dyn std::error::Error + 'static)> {
119 self.source.iter().map(|v| v.as_ref())
120 }
121
122 pub fn from_io_error(err: io::Error) -> Error {
124 Error::new("failed to perform io").with_source(err)
125 }
126
127 pub fn from_fmt_error(err: fmt::Error) -> Error {
129 Error::new("failed to perform format").with_source(err)
130 }
131}