Skip to main content

n0_snafu/
error.rs

1use color_backtrace::Verbosity;
2use snafu::{FromString, GenerateImplicitData, Snafu};
3use tracing_error::SpanTraceStatus;
4
5use crate::SpanTrace;
6
7pub type Result<A = (), E = Error> = std::result::Result<A, E>;
8
9#[macro_export]
10macro_rules! format_err {
11    ($fmt:literal$(, $($arg:expr),* $(,)?)?) => {
12        {
13            let err: $crate::Error = ::snafu::FromString::without_source(
14                format!($fmt$(, $($arg),*)*),
15            );
16            err
17        }
18    };
19}
20
21pub trait ResultExt<T> {
22    #[track_caller]
23    fn context<C>(self, context: C) -> Result<T, Error>
24    where
25        C: AsRef<str>;
26
27    #[track_caller]
28    fn with_context<F>(self, context: F) -> Result<T, Error>
29    where
30        F: FnOnce() -> String;
31
32    /// Quickly convert a std error into a `Error`, without having to write a `context` message.
33    #[track_caller]
34    fn e(self) -> Result<T, Error>;
35}
36
37impl<T, E> ResultExt<T> for Result<T, E>
38where
39    E: snafu::Error + Sync + Send + 'static,
40{
41    #[track_caller]
42    fn context<C>(self, context: C) -> Result<T, Error>
43    where
44        C: AsRef<str>,
45    {
46        match self {
47            Ok(v) => Ok(v),
48            Err(error) => Err(Error::Message {
49                message: Some(context.as_ref().into()),
50                span_trace: GenerateImplicitData::generate(),
51                source: Box::new(error),
52                backtrace: GenerateImplicitData::generate(),
53            }),
54        }
55    }
56
57    #[track_caller]
58    fn e(self) -> Result<T, Error> {
59        match self {
60            Ok(v) => Ok(v),
61            Err(error) => Err(Error::Message {
62                message: None,
63                span_trace: GenerateImplicitData::generate(),
64                source: Box::new(error),
65                backtrace: GenerateImplicitData::generate(),
66            }),
67        }
68    }
69
70    #[track_caller]
71    fn with_context<F>(self, context: F) -> Result<T, Error>
72    where
73        F: FnOnce() -> String,
74    {
75        match self {
76            Ok(v) => Ok(v),
77            Err(error) => Err(Error::Message {
78                message: Some(context()),
79                span_trace: GenerateImplicitData::generate(),
80                source: Box::new(error),
81                backtrace: GenerateImplicitData::generate(),
82            }),
83        }
84    }
85}
86
87impl<T> ResultExt<T> for Result<T, Error> {
88    #[track_caller]
89    fn context<C>(self, context: C) -> Result<T, Error>
90    where
91        C: AsRef<str>,
92    {
93        match self {
94            Ok(v) => Ok(v),
95            Err(error) => Err(Error::Whatever {
96                message: Some(context.as_ref().into()),
97                span_trace: GenerateImplicitData::generate(),
98                source: Some(Box::new(error)),
99                backtrace: GenerateImplicitData::generate(),
100            }),
101        }
102    }
103
104    #[track_caller]
105    fn e(self) -> Result<T, Error> {
106        match self {
107            Ok(v) => Ok(v),
108            Err(error) => Err(Error::Whatever {
109                message: None,
110                span_trace: GenerateImplicitData::generate(),
111                source: Some(Box::new(error)),
112                backtrace: GenerateImplicitData::generate(),
113            }),
114        }
115    }
116    #[track_caller]
117    fn with_context<F>(self, context: F) -> Result<T, Error>
118    where
119        F: FnOnce() -> String,
120    {
121        match self {
122            Ok(v) => Ok(v),
123            Err(error) => Err(Error::Whatever {
124                message: Some(context()),
125                span_trace: GenerateImplicitData::generate(),
126                source: Some(Box::new(error)),
127                backtrace: GenerateImplicitData::generate(),
128            }),
129        }
130    }
131}
132
133#[derive(Debug, Snafu)]
134#[snafu(display("Expected some, found none"))]
135struct NoneError;
136
137impl<T> ResultExt<T> for Option<T> {
138    #[track_caller]
139    fn context<C>(self, context: C) -> Result<T, Error>
140    where
141        C: AsRef<str>,
142    {
143        match self {
144            Some(v) => Ok(v),
145            None => Err(Error::Message {
146                message: Some(context.as_ref().into()),
147                span_trace: GenerateImplicitData::generate(),
148                source: Box::new(NoneError),
149                backtrace: GenerateImplicitData::generate(),
150            }),
151        }
152    }
153
154    #[track_caller]
155    fn e(self) -> Result<T, Error> {
156        match self {
157            Some(v) => Ok(v),
158            None => Err(Error::Message {
159                message: None,
160                span_trace: GenerateImplicitData::generate(),
161                source: Box::new(NoneError),
162                backtrace: GenerateImplicitData::generate(),
163            }),
164        }
165    }
166
167    #[track_caller]
168    fn with_context<F>(self, context: F) -> Result<T, Error>
169    where
170        F: FnOnce() -> String,
171    {
172        match self {
173            Some(v) => Ok(v),
174            None => Err(Error::Message {
175                message: Some(context()),
176                span_trace: GenerateImplicitData::generate(),
177                source: Box::new(NoneError),
178                backtrace: GenerateImplicitData::generate(),
179            }),
180        }
181    }
182}
183
184// Trait safe version
185pub trait Formatted: snafu::Error {
186    /// Returns a [`Backtrace`][] that may be printed.
187    fn backtrace(&self) -> Option<Backtrace<'_>>;
188}
189
190impl<T: snafu::Error + snafu::ErrorCompat> Formatted for T {
191    fn backtrace(&self) -> Option<Backtrace<'_>> {
192        snafu::ErrorCompat::backtrace(self).map(Backtrace::Crate)
193    }
194}
195
196pub enum Error {
197    Source {
198        source: Box<dyn Formatted + Sync + Send + 'static>,
199        span_trace: SpanTrace,
200        backtrace: Option<snafu::Backtrace>,
201    },
202    Message {
203        message: Option<String>,
204        span_trace: SpanTrace,
205        source: Box<dyn snafu::Error + Sync + Send + 'static>,
206        backtrace: Option<snafu::Backtrace>,
207    },
208    Anyhow {
209        source: anyhow::Error,
210        span_trace: SpanTrace,
211        backtrace: Option<snafu::Backtrace>,
212    },
213    Whatever {
214        message: Option<String>,
215        span_trace: SpanTrace,
216        source: Option<Box<Error>>,
217        backtrace: Option<snafu::Backtrace>,
218    },
219}
220
221impl<E1: Formatted + Send + Sync + 'static> From<E1> for Error {
222    fn from(value: E1) -> Self {
223        Self::Source {
224            source: Box::new(value),
225            span_trace: GenerateImplicitData::generate(),
226            backtrace: GenerateImplicitData::generate(),
227        }
228    }
229}
230
231impl FromString for Error {
232    type Source = Error;
233
234    fn without_source(message: String) -> Self {
235        Self::Whatever {
236            message: Some(message),
237            span_trace: GenerateImplicitData::generate(),
238            backtrace: GenerateImplicitData::generate(),
239            source: None,
240        }
241    }
242
243    fn with_source(source: Error, message: String) -> Self {
244        Self::Whatever {
245            message: Some(message),
246            span_trace: GenerateImplicitData::generate(),
247            backtrace: GenerateImplicitData::generate(),
248            source: Some(Box::new(source)),
249        }
250    }
251}
252
253impl std::fmt::Debug for Error {
254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255        let verb = Verbosity::from_env();
256
257        let filters = [
258            "<n0_snafu::testerror::Error",
259            "n0_snafu::testerror::Error::anyhow",
260            "<core::pin::Pin<P> as core::future::future::Future>::poll",
261            "<core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual",
262        ];
263
264        let mut printer =
265            color_backtrace::BacktracePrinter::new().add_frame_filter(Box::new(move |frames| {
266                frames.retain(|frame| {
267                    frame
268                        .name
269                        .as_ref()
270                        .map(|name| {
271                            for f in &filters {
272                                if name.starts_with(f) {
273                                    return false;
274                                }
275                            }
276                            true
277                        })
278                        .unwrap_or(true)
279                })
280            }));
281
282        if verb != Verbosity::Full {
283            printer = printer.add_frame_filter(Box::new(|frames| {
284                frames.retain(|frame| !frame.is_dependency_code())
285            }));
286        }
287
288        let stack = self.stack();
289
290        write!(f, "{self:#}")?;
291
292        // Span Trace
293        if self.span_trace().status() == SpanTraceStatus::CAPTURED {
294            writeln!(f, "Span trace:")?;
295            writeln!(f, "{}\n", self.span_trace())?;
296        }
297
298        // Backtrace
299        for (bt, _source) in stack.into_iter() {
300            if let Some(bt) = bt {
301                let s = printer.format_trace_to_string(&bt).unwrap();
302                writeln!(f, "\n{s}")?;
303            }
304        }
305        Ok(())
306    }
307}
308
309impl Error {
310    pub fn span_trace(&self) -> &SpanTrace {
311        match self {
312            Self::Source { span_trace, .. } => span_trace,
313            Self::Message { span_trace, .. } => span_trace,
314            Self::Anyhow { span_trace, .. } => span_trace,
315            Self::Whatever { span_trace, .. } => span_trace,
316        }
317    }
318
319    pub fn backtrace(&self) -> Option<Backtrace<'_>> {
320        let backtrace = match self {
321            Self::Source { backtrace, .. } => backtrace.as_ref(),
322            Self::Message { backtrace, .. } => backtrace.as_ref(),
323            Self::Anyhow { backtrace, .. } => backtrace.as_ref(),
324            Self::Whatever { backtrace, .. } => backtrace.as_ref(),
325        };
326        backtrace.map(Backtrace::Crate)
327    }
328
329    pub fn anyhow(err: anyhow::Error) -> Self {
330        Self::Anyhow {
331            source: err,
332            span_trace: GenerateImplicitData::generate(),
333            backtrace: GenerateImplicitData::generate(),
334        }
335    }
336
337    pub fn stack(&self) -> Vec<(Option<Backtrace<'_>>, Source<'_>)> {
338        let mut traces = Vec::new();
339        match self {
340            Self::Source {
341                source, backtrace, ..
342            } => {
343                // current trace
344                traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
345                traces.push((source.backtrace(), Source::Formatted(source.as_ref())));
346
347                // collect the traces from our sources
348                let mut source = source.source();
349
350                while let Some(s) = source {
351                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
352                        traces.push((this.backtrace(), Source::Formatted(*this)));
353                    } else {
354                        traces.push((None, Source::SnafuError(s)));
355                    }
356                    source = s.source();
357                }
358            }
359            Self::Message {
360                source, backtrace, ..
361            } => {
362                // current trace
363                traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
364
365                // collect the traces from our sources
366                let mut source: Option<&(dyn snafu::Error + 'static)> = Some(source.as_ref());
367
368                while let Some(s) = source {
369                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
370                        traces.push((this.backtrace(), Source::Formatted(*this)));
371                    } else {
372                        traces.push((None, Source::SnafuError(s)));
373                    }
374                    source = s.source();
375                }
376            }
377            Self::Anyhow {
378                source, backtrace, ..
379            } => {
380                // current trace
381                traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
382
383                traces.push((
384                    Some(Backtrace::Std(source.backtrace())),
385                    Source::Anyhow(source),
386                ));
387
388                for s in source.chain().skip(1) {
389                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
390                        traces.push((this.backtrace(), Source::Formatted(*this)));
391                    } else {
392                        traces.push((None, Source::SnafuError(s)));
393                    }
394                }
395            }
396            Self::Whatever {
397                source, backtrace, ..
398            } => {
399                // current trace
400                traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
401
402                // collect the traces from our sources
403                if let Some(s) = source.as_deref() {
404                    traces.push((s.backtrace(), Source::Error(s)));
405                    s.stack_inner(&mut traces);
406                }
407            }
408        }
409
410        traces
411    }
412
413    fn stack_inner<'a>(&'a self, traces: &mut Vec<(Option<Backtrace<'a>>, Source<'a>)>) {
414        match self {
415            Self::Source { source, .. } => {
416                traces.push((source.backtrace(), Source::Formatted(source.as_ref())));
417
418                // collect the traces from our sources
419                let mut source = source.source();
420
421                while let Some(s) = source {
422                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
423                        traces.push((this.backtrace(), Source::Formatted(*this)));
424                    } else {
425                        traces.push((None, Source::SnafuError(s)));
426                    }
427                    source = s.source();
428                }
429            }
430            Self::Message { source, .. } => {
431                // collect the traces from our sources
432                let mut source: Option<&(dyn snafu::Error + 'static)> = Some(source.as_ref());
433
434                while let Some(s) = source {
435                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
436                        traces.push((this.backtrace(), Source::Formatted(*this)));
437                    } else {
438                        traces.push((None, Source::SnafuError(s)));
439                    }
440                    source = s.source();
441                }
442            }
443            Self::Anyhow { source, .. } => {
444                traces.push((
445                    Some(Backtrace::Std(source.backtrace())),
446                    Source::Anyhow(source),
447                ));
448
449                for s in source.chain().skip(1) {
450                    if let Some(this) = s.downcast_ref::<&dyn Formatted>() {
451                        traces.push((this.backtrace(), Source::Formatted(*this)));
452                    } else {
453                        traces.push((None, Source::SnafuError(s)));
454                    }
455                }
456            }
457            Self::Whatever { source, .. } => {
458                // collect the traces from our sources
459                if let Some(s) = source.as_deref() {
460                    traces.push((s.backtrace(), Source::Error(s)));
461                    let stack = s.stack();
462                    traces.extend(stack);
463                }
464            }
465        }
466    }
467}
468
469#[derive(Clone, Debug)]
470pub enum Backtrace<'a> {
471    Crate(&'a snafu::Backtrace),
472    Std(&'a std::backtrace::Backtrace),
473}
474
475impl color_backtrace::Backtrace for Backtrace<'_> {
476    fn frames(&self) -> Vec<color_backtrace::Frame> {
477        match self {
478            Self::Crate(bt) => color_backtrace::Backtrace::frames(*bt),
479            Self::Std(bt) => {
480                // no comment, things are sad in std land
481                let parsed_bt = btparse::deserialize(bt).expect("failed to parse stacks");
482                color_backtrace::Backtrace::frames(&parsed_bt)
483            }
484        }
485    }
486}
487
488pub enum Source<'a> {
489    Root,
490    Formatted(&'a dyn Formatted),
491    SnafuError(&'a dyn snafu::Error),
492    Error(&'a Error),
493    Anyhow(&'a anyhow::Error),
494}
495
496impl core::fmt::Display for Source<'_> {
497    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
498        match self {
499            Self::Root => write!(f, "Root"),
500            Self::Formatted(e) => e.fmt(f),
501            Self::Error(e) => e.fmt(f),
502            Self::SnafuError(e) => e.fmt(f),
503            Self::Anyhow(e) => e.fmt(f),
504        }
505    }
506}
507
508impl snafu::ErrorCompat for Error {
509    fn backtrace(&self) -> Option<&snafu::Backtrace> {
510        self.stack().last().and_then(|(bt, _)| match *bt {
511            Some(Backtrace::Crate(bt)) => Some(bt),
512            _ => None,
513        })
514    }
515}
516
517trait ErrorSource<'a>: std::fmt::Display + std::fmt::Debug {
518    fn source(&'a self) -> Option<SourceWrapper<'a>>;
519}
520
521impl<'a> ErrorSource<'a> for Error {
522    fn source(&'a self) -> Option<SourceWrapper<'a>> {
523        match self {
524            Error::Source { source, .. } => source.source().map(SourceWrapper::Std),
525            Error::Anyhow { source, .. } => source.source().map(SourceWrapper::Std),
526            Error::Message { ref source, .. } => Some(SourceWrapper::Box(source)),
527            Error::Whatever { ref source, .. } => source.as_ref().map(|s| SourceWrapper::Crate(s)),
528        }
529    }
530}
531
532#[derive(Debug, Clone, Copy)]
533enum SourceWrapper<'a> {
534    Std(&'a dyn std::error::Error),
535    #[allow(clippy::borrowed_box)]
536    Box(&'a Box<dyn snafu::Error + Sync + Send + 'static>),
537    Crate(&'a Error),
538}
539
540impl<'a> std::fmt::Display for SourceWrapper<'a> {
541    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542        match self {
543            SourceWrapper::Std(error) => write!(f, "{error}"),
544            SourceWrapper::Crate(error) => match error {
545                Error::Message { message, .. } => {
546                    write!(f, "{}", message.as_deref().unwrap_or("Error"))
547                }
548                _ => write!(f, "{error}"),
549            },
550            SourceWrapper::Box(error) => write!(f, "{error}"),
551        }
552    }
553}
554
555impl<'a> ErrorSource<'a> for SourceWrapper<'a> {
556    fn source(&'a self) -> Option<SourceWrapper<'a>> {
557        match self {
558            SourceWrapper::Std(error) => std::error::Error::source(error).map(SourceWrapper::Std),
559            SourceWrapper::Crate(error) => error.source(),
560            SourceWrapper::Box(error) => error.source().map(SourceWrapper::Std),
561        }
562    }
563}
564
565fn write_sources_if_alternate(
566    f: &mut core::fmt::Formatter,
567    source: Option<SourceWrapper<'_>>,
568) -> core::fmt::Result {
569    if !f.alternate() {
570        return Ok(());
571    }
572    write_sources(f, source)?;
573    Ok(())
574}
575
576fn write_sources(
577    f: &mut core::fmt::Formatter,
578    source: Option<SourceWrapper<'_>>,
579) -> core::fmt::Result {
580    write_sources_inner(f, source, 0)?;
581    Ok(())
582}
583
584fn write_sources_inner(
585    f: &mut core::fmt::Formatter,
586    source: Option<SourceWrapper<'_>>,
587    i: usize,
588) -> core::fmt::Result {
589    if let Some(current) = source {
590        write!(f, "\n  {i}: {current}")?;
591        write_sources_inner(f, current.source(), i + 1)?;
592    }
593    Ok(())
594}
595
596impl core::fmt::Display for Error {
597    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
598        match self {
599            Self::Source { source, .. } => {
600                write!(f, "{source}")?;
601            }
602            Self::Whatever {
603                message, source, ..
604            } => match (source, message) {
605                (Some(source), Some(message)) => {
606                    if f.alternate() {
607                        write!(f, "{message}")?;
608                    } else {
609                        write!(f, "{message}: {source}")?;
610                    }
611                }
612                (None, Some(message)) => {
613                    write!(f, "{message}")?;
614                }
615                (Some(source), None) => {
616                    write!(f, "{source}")?;
617                }
618                (None, None) => {
619                    write!(f, "Error")?;
620                }
621            },
622            Self::Message {
623                message, source, ..
624            } => {
625                if let Some(message) = message {
626                    write!(f, "{message}: {source}")?;
627                } else {
628                    write!(f, "{source}")?;
629                }
630            }
631            Self::Anyhow { source, .. } => source.fmt(f)?,
632        }
633        write_sources_if_alternate(f, self.source())
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use snafu::Snafu;
640
641    use super::*;
642
643    #[test]
644    fn test_anyhow_compat() -> Result {
645        fn ok() -> anyhow::Result<()> {
646            Ok(())
647        }
648
649        ok().map_err(Error::anyhow)?;
650
651        Ok(())
652    }
653
654    #[derive(Debug, Snafu)]
655    enum MyError {
656        #[snafu(display("A failure"))]
657        A,
658    }
659
660    #[test]
661    fn test_whatever() {
662        fn fail() -> Result {
663            snafu::whatever!("sad face");
664        }
665
666        fn fail_my_error() -> Result<(), MyError> {
667            Err(ASnafu.build())
668        }
669
670        fn fail_whatever() -> Result {
671            snafu::whatever!(fail(), "sad");
672            Ok(())
673        }
674
675        fn fail_whatever_my_error() -> Result {
676            snafu::whatever!(fail_my_error(), "sad");
677            Ok(())
678        }
679
680        assert!(fail().is_err());
681        assert_eq!(format!("{:?}", fail().unwrap_err()), "sad face");
682        assert_eq!(format!("{}", fail().unwrap_err()), "sad face");
683        assert!(fail_my_error().is_err());
684        assert!(fail_whatever().is_err());
685        assert!(fail_whatever_my_error().is_err());
686
687        assert_eq!(
688            format!("{:?}", fail_whatever().unwrap_err()),
689            "sad\n  0: sad face"
690        );
691
692        assert_eq!(format!("{:?}", fail_whatever()), "Err(sad\n  0: sad face)");
693    }
694
695    #[test]
696    fn test_context_none() {
697        fn fail() -> Result {
698            None.context("sad")
699        }
700
701        assert!(fail().is_err());
702    }
703
704    #[test]
705    fn test_format_err() {
706        fn fail() -> Result {
707            Err(format_err!("sad: {}", 12))
708        }
709
710        assert!(fail().is_err());
711    }
712
713    #[test]
714    fn test_io_err() {
715        fn fail_io() -> std::io::Result<()> {
716            Err(std::io::Error::other("sad IO"))
717        }
718
719        fn fail_custom() -> Result<(), MyError> {
720            Ok(())
721        }
722
723        fn fail_outer() -> Result {
724            fail_io().e()?;
725            fail_custom()?;
726            Ok(())
727        }
728        let err = fail_outer().unwrap_err();
729        assert_eq!(err.to_string(), "sad IO");
730    }
731
732    #[test]
733    fn test_message() {
734        fn fail_box() -> Result<(), impl snafu::Error + Send + Sync + 'static> {
735            Err(Box::new(std::io::Error::other("foo")))
736        }
737
738        let my_res = fail_box().context("failed");
739
740        let err = my_res.unwrap_err();
741        let stack = err.stack();
742        assert_eq!(stack.len(), 2);
743    }
744
745    #[test]
746    fn test_option() {
747        fn fail_opt() -> Option<()> {
748            None
749        }
750
751        let my_res = fail_opt().context("failed");
752
753        let err = my_res.unwrap_err();
754        let stack = err.stack();
755        assert_eq!(stack.len(), 2);
756    }
757
758    #[test]
759    fn test_sources() {
760        let err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
761        let file_name = "foo.txt";
762        let res: Result<(), _> = Err(err).with_context(|| format!("failed to read {file_name}"));
763        let res: Result<(), _> = res.context("read error");
764
765        let err = res.err().unwrap();
766
767        let fmt = format!("{err}");
768        println!("short:\n{fmt}\n");
769        assert_eq!(&fmt, "read error: failed to read foo.txt: file not found");
770
771        let fmt = format!("{err:#}");
772        println!("alternate:\n{fmt}\n");
773        assert_eq!(
774            &fmt,
775            r#"read error
776  0: failed to read foo.txt
777  1: file not found"#
778        );
779
780        let fmt = format!("{err:?}");
781        println!("debug:\n{fmt}\n");
782    }
783}