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 #[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
184pub trait Formatted: snafu::Error {
186 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 if self.span_trace().status() == SpanTraceStatus::CAPTURED {
294 writeln!(f, "Span trace:")?;
295 writeln!(f, "{}\n", self.span_trace())?;
296 }
297
298 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 traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
345 traces.push((source.backtrace(), Source::Formatted(source.as_ref())));
346
347 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 traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
364
365 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 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 traces.push((backtrace.as_ref().map(Backtrace::Crate), Source::Root));
401
402 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 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 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 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 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}