1use crate::ChainCompat;
2use core::{fmt, panic::Location};
3
4#[cfg(feature = "std")]
5use std::process::{ExitCode, Termination};
6
7#[cfg(feature = "alloc")]
8use alloc::string::{String, ToString};
9
10pub struct Report<E>(Result<(), E>);
64
65impl<E> Report<E> {
66 pub fn from_error(error: E) -> Self {
84 Self(Err(error))
85 }
86
87 pub fn capture(body: impl FnOnce() -> Result<(), E>) -> Self {
109 Self(body())
110 }
111
112 pub const fn ok() -> Self {
114 Self(Ok(()))
115 }
116}
117
118impl<E> From<Result<(), E>> for Report<E> {
119 fn from(other: Result<(), E>) -> Self {
120 Self(other)
121 }
122}
123
124impl<E> fmt::Debug for Report<E>
125where
126 E: crate::Error,
127{
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 fmt::Display::fmt(self, f)
130 }
131}
132
133impl<E> fmt::Display for Report<E>
134where
135 E: crate::Error,
136{
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match &self.0 {
139 Err(e) => fmt::Display::fmt(&ReportFormatter(e), f),
140 _ => Ok(()),
141 }
142 }
143}
144
145#[cfg(feature = "std")]
146impl<E> Termination for Report<E>
147where
148 E: crate::Error,
149{
150 fn report(self) -> ExitCode {
151 match self.0 {
152 Ok(()) => ExitCode::SUCCESS,
153 Err(e) => {
154 std::eprintln!("Error: {}", ReportFormatter(&e));
155
156 #[cfg(feature = "unstable-provider-api")]
157 {
158 use crate::error;
159
160 ChainCompat::new(&e)
161 .find_map(|e| {
162 error::request_value(e).or_else(|| error::request_ref(e).copied())
163 })
164 .unwrap_or(ExitCode::FAILURE)
165 }
166
167 #[cfg(not(feature = "unstable-provider-api"))]
168 {
169 ExitCode::FAILURE
170 }
171 }
172 }
173 }
174}
175
176fn request_location(e: &dyn crate::Error) -> Option<&Location<'static>> {
177 #[cfg(feature = "unstable-provider-api")]
178 {
179 use crate::error;
180
181 error::request_ref::<&'static Location>(e)
182 .copied()
183 .or_else(|| error::request_ref::<Location>(e))
184 .or_else(|| error::request_value::<&'static Location>(e))
185 }
186
187 #[cfg(not(feature = "unstable-provider-api"))]
188 {
189 let _e = e;
190 None
191 }
192}
193
194#[cfg(feature = "unstable-try-trait")]
195impl<T, E> core::ops::FromResidual<Result<T, E>> for Report<E> {
196 fn from_residual(residual: Result<T, E>) -> Self {
197 Self(residual.map(drop))
198 }
199}
200
201struct ReportFormatter<'a>(&'a dyn crate::Error);
202
203impl<'a> fmt::Display for ReportFormatter<'a> {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 #[cfg(feature = "std")]
206 {
207 if trace_cleaning_enabled() {
208 self.cleaned_error_trace(f)?;
209 } else {
210 self.error_trace(f)?;
211 }
212 }
213
214 #[cfg(not(feature = "std"))]
215 {
216 self.error_trace(f)?;
217 }
218
219 #[cfg(feature = "unstable-provider-api")]
220 {
221 if let Some(bt) = crate::backtraces(self.0).last() {
222 writeln!(f, "\nBacktrace:\n{}", bt)?;
223 }
224 }
225
226 Ok(())
227 }
228}
229
230impl<'a> ReportFormatter<'a> {
231 fn error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
232 writeln!(f, "{}", AddLocation(self.0))?;
233
234 let sources = ChainCompat::new(self.0).skip(1);
235 let plurality = sources.clone().take(2).count();
236
237 match plurality {
238 0 => {}
239 1 => writeln!(f, "\nCaused by this error:")?,
240 _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
241 }
242
243 for (i, source) in sources.enumerate() {
244 let i = i + 1;
246 writeln!(f, "{:3}: {}", i, AddLocation(source))?;
247 }
248
249 Ok(())
250 }
251
252 #[cfg(feature = "std")]
253 fn cleaned_error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
254 use alloc::vec::Vec;
255
256 const NOTE: char = '*';
257
258 let mut any_cleaned = false;
259 let mut any_removed = false;
260 let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0)
261 .flat_map(|(e, mut msg, cleaned)| {
262 if msg.is_empty() {
263 any_removed = true;
264 None
265 } else {
266 if let Some(l) = request_location(e) {
267 use core::fmt::Write;
268 write!(msg, " ({})", l).unwrap();
269 }
270
271 if cleaned {
272 any_cleaned = true;
273 msg.push(' ');
274 msg.push(NOTE);
275 }
276 Some(msg)
277 }
278 })
279 .collect();
280
281 let mut visible_messages = cleaned_messages.iter();
282
283 let head = match visible_messages.next() {
284 Some(v) => v,
285 None => return Ok(()),
286 };
287
288 writeln!(f, "{}", head)?;
289
290 match cleaned_messages.len() {
291 0 | 1 => {}
292 2 => writeln!(f, "\nCaused by this error:")?,
293 _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
294 }
295
296 for (i, msg) in visible_messages.enumerate() {
297 let i = i + 1;
299 writeln!(f, "{:3}: {}", i, msg)?;
300 }
301
302 if any_cleaned || any_removed {
303 write!(f, "\nNOTE: ")?;
304
305 if any_cleaned {
306 write!(
307 f,
308 "Some redundant information has been removed from the lines marked with {}. ",
309 NOTE,
310 )?;
311 } else {
312 write!(f, "Some redundant information has been removed. ")?;
313 }
314
315 writeln!(
316 f,
317 "Set {}=1 to disable this behavior.",
318 SNAFU_RAW_ERROR_MESSAGES,
319 )?;
320 }
321
322 Ok(())
323 }
324}
325
326struct AddLocation<E>(E);
327
328impl<E: crate::Error> fmt::Display for AddLocation<E> {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(f, "{}", self.0)?;
331 if let Some(l) = request_location(&self.0) {
332 write!(f, " ({})", l)?;
333 }
334 Ok(())
335 }
336}
337
338#[cfg(feature = "std")]
339const SNAFU_RAW_ERROR_MESSAGES: &str = "SNAFU_RAW_ERROR_MESSAGES";
340
341#[cfg(feature = "std")]
342fn trace_cleaning_enabled() -> bool {
343 use crate::once_bool::OnceBool;
344 use std::env;
345
346 static DISABLED: OnceBool = OnceBool::new();
347 !DISABLED.get(|| env::var_os(SNAFU_RAW_ERROR_MESSAGES).map_or(false, |v| v == "1"))
348}
349
350#[cfg(feature = "alloc")]
379pub struct CleanedErrorText<'a>(Option<CleanedErrorTextStep<'a>>);
380
381#[cfg(feature = "alloc")]
382impl<'a> CleanedErrorText<'a> {
383 pub fn new(error: &'a dyn crate::Error) -> Self {
385 Self(Some(CleanedErrorTextStep::new(error)))
386 }
387}
388
389#[cfg(feature = "alloc")]
390impl<'a> Iterator for CleanedErrorText<'a> {
391 type Item = (&'a dyn crate::Error, String, bool);
393
394 fn next(&mut self) -> Option<Self::Item> {
395 use core::mem;
396
397 let mut step = self.0.take()?;
398 let mut error_text = mem::take(&mut step.error_text);
399
400 match step.error.source() {
401 Some(next_error) => {
402 let next_error_text = next_error.to_string();
403
404 let cleaned_text = error_text
405 .trim_end_matches(&next_error_text)
406 .trim_end()
407 .trim_end_matches(':');
408 let cleaned = cleaned_text.len() != error_text.len();
409 let cleaned_len = cleaned_text.len();
410 error_text.truncate(cleaned_len);
411
412 self.0 = Some(CleanedErrorTextStep {
413 error: next_error,
414 error_text: next_error_text,
415 });
416
417 Some((step.error, error_text, cleaned))
418 }
419 None => Some((step.error, error_text, false)),
420 }
421 }
422}
423
424#[cfg(feature = "alloc")]
425struct CleanedErrorTextStep<'a> {
426 error: &'a dyn crate::Error,
427 error_text: String,
428}
429
430#[cfg(feature = "alloc")]
431impl<'a> CleanedErrorTextStep<'a> {
432 fn new(error: &'a dyn crate::Error) -> Self {
433 let error_text = error.to_string();
434 Self { error, error_text }
435 }
436}
437
438#[doc(hidden)]
439pub trait __InternalExtractErrorType {
440 type Err;
441}
442
443impl<T, E> __InternalExtractErrorType for core::result::Result<T, E> {
444 type Err = E;
445}