1mod clean;
2
3pub use clean::{CleanedErrorText, CleanedErrors};
4
5use core::fmt;
6use std::error::Error;
7
8pub trait Reportable {
11 fn report(self) -> Report<Self>
12 where
13 Self: std::error::Error,
14 Self: std::marker::Sized;
15}
16
17impl<E: Error> Reportable for E {
18 fn report(self) -> Report<Self>
19 where
20 Self: std::error::Error,
21 Self: std::marker::Sized,
22 {
23 Report::new(self)
24 }
25}
26
27pub trait AsRefError {
31 fn as_ref_error(&self) -> &dyn Error;
32}
33
34impl<E: Error> AsRefError for E {
35 fn as_ref_error(&self) -> &dyn Error {
36 self
37 }
38}
39
40pub struct Report<E: AsRefError>(E);
79
80impl<E: AsRefError> From<E> for Report<E> {
81 fn from(value: E) -> Self {
82 Self(value)
83 }
84}
85
86impl<E: AsRefError> Report<E> {
87 pub fn new(err: E) -> Self {
89 Self::from(err)
90 }
91
92 fn format(&self, f: &mut fmt::Formatter<'_>, multiline: bool) -> fmt::Result {
93 let cleaned_texts = CleanedErrorText::new(self.0.as_ref_error())
94 .filter(|(_, t, _)| !t.is_empty())
95 .enumerate();
96
97 if !multiline {
98 for (i, (_, text, _)) in cleaned_texts {
99 if i > 0 {
100 write!(f, ": ")?;
101 }
102 write!(f, "{text}")?;
103 }
104 } else {
105 for (i, (_, text, _)) in cleaned_texts {
106 if i == 0 {
107 write!(f, "{text}")?;
108 } else {
109 if i == 1 {
110 write!(f, "\n\nCaused by:\n")?;
111 }
112 writeln!(f, " {i}. {text}")?;
113 }
114 }
115 }
116
117 Ok(())
118 }
119}
120
121impl<E: AsRefError> fmt::Debug for Report<E> {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 self.format(f, true)
124 }
125}
126
127impl<E: AsRefError> fmt::Display for Report<E> {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 self.format(f, f.alternate())
130 }
131}
132
133pub struct Ref<E>(E);
136
137impl<E: AsRef<dyn Error>> AsRefError for Ref<E> {
138 fn as_ref_error(&self) -> &dyn Error {
139 self.0.as_ref()
140 }
141}
142
143impl<E: AsRef<dyn Error>> Report<Ref<E>> {
144 pub fn from_ref(value: E) -> Self {
146 Self::new(Ref(value))
147 }
148}
149
150impl<E: AsRef<dyn Error>> From<E> for Report<Ref<E>> {
151 fn from(value: E) -> Self {
152 Self::from_ref(value)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use anyhow::Context;
160
161 #[test]
162 fn test_conversion() {
163 fn anyhow_fn() -> anyhow::Result<()> {
164 Err(anyhow::anyhow!("oh no!"))
165 }
166
167 fn anyhow_caller() -> Result<(), super::Report<impl AsRefError>> {
168 anyhow_fn().context("fn failed")?;
169
170 Ok(())
171 }
172
173 #[derive(Debug)]
174 struct CustomError();
175
176 impl std::fmt::Display for CustomError {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 write!(f, "custom error")
179 }
180 }
181
182 impl std::error::Error for CustomError {}
183
184 fn custom_fn() -> Result<(), CustomError> {
185 Err(CustomError())
186 }
187
188 fn custom_caller() -> Result<(), super::Report<CustomError>> {
189 custom_fn()?;
190
191 Ok(())
192 }
193
194 let err = anyhow_caller().expect_err("function did not return error");
195
196 let normal_string = format!("{}", err);
197 let alt_string = format!("{:#}", err);
198 let debug_string = format!("{:?}", err);
199
200 assert_eq!(normal_string, "fn failed: oh no!");
201 assert_eq!(alt_string, "fn failed\n\nCaused by:\n 1. oh no!\n");
202 assert_eq!(debug_string, "fn failed\n\nCaused by:\n 1. oh no!\n");
203
204 let err = custom_caller().expect_err("function did not return error");
205
206 let normal_string = format!("{}", err);
207 let alt_string = format!("{:#}", err);
208 let debug_string = format!("{:?}", err);
209
210 assert_eq!(normal_string, "custom error");
211 assert_eq!(alt_string, "custom error");
212 assert_eq!(debug_string, "custom error");
213
214 let err = custom_fn().expect_err("function did not return error");
215 let report = Report::from(&err);
216 let normal_string = format!("{}", report);
217 let alt_string = format!("{:#}", report);
218 let debug_string = format!("{:?}", report);
219
220 assert_eq!(normal_string, "custom error");
221 assert_eq!(alt_string, "custom error");
222 assert_eq!(debug_string, "custom error");
223 _ = err
224 }
225}