1use std::error::Error as StdError;
4use std::fmt;
5
6use crate::{Backtrace, Capturable as _, Diagnostic, SpanTrace};
7
8type BoxError = Box<dyn StdError + Send + Sync + 'static>;
10
11#[must_use = "this `Welp` error should be returned or propagated, not discarded"]
66pub struct Welp(WelpRepr);
67
68enum WelpRepr {
69 Sourced {
70 message: Option<Box<str>>,
74 source: BoxError,
75 traces: Box<(Backtrace, SpanTrace)>,
76 location: &'static std::panic::Location<'static>,
77 },
78 Traced {
79 message: Box<str>,
80 traces: Box<(Backtrace, SpanTrace)>,
81 location: &'static std::panic::Location<'static>,
82 },
83}
84
85impl Welp {
86 #[track_caller]
96 pub fn new(message: impl Into<String>) -> Self {
97 Self(WelpRepr::Traced {
98 message: message.into().into_boxed_str(),
99 traces: Box::new((Backtrace::capture(), SpanTrace::capture())),
100 location: std::panic::Location::caller(),
101 })
102 }
103
104 #[track_caller]
121 pub fn wrap<E>(source: E, message: impl Into<String>) -> Self
122 where
123 E: StdError + Send + Sync + 'static,
124 {
125 Self(WelpRepr::Sourced {
131 message: Some(message.into().into_boxed_str()),
132 source: Box::new(source),
133 traces: Box::new((Backtrace::capture(), SpanTrace::capture())),
134 location: std::panic::Location::caller(),
135 })
136 }
137
138 #[track_caller]
155 pub fn wrap_boxed(source: BoxError, message: impl Into<String>) -> Self {
156 Self(WelpRepr::Sourced {
157 message: Some(message.into().into_boxed_str()),
158 source,
159 traces: Box::new((Backtrace::capture(), SpanTrace::capture())),
160 location: std::panic::Location::caller(),
161 })
162 }
163
164 #[track_caller]
187 pub fn from_error<E>(source: E) -> Self
188 where
189 E: StdError + Send + Sync + 'static,
190 {
191 Self(WelpRepr::Sourced {
192 message: None,
193 source: Box::new(source),
194 traces: Box::new((Backtrace::capture(), SpanTrace::capture())),
195 location: std::panic::Location::caller(),
196 })
197 }
198
199 #[must_use]
206 #[inline]
207 pub fn message(&self) -> Option<&str> {
208 match &self.0 {
209 WelpRepr::Sourced { message, .. } => message.as_deref(),
210 WelpRepr::Traced { message, .. } => Some(message),
211 }
212 }
213
214 #[must_use]
239 #[inline]
240 pub fn downcast_ref<E: StdError + 'static>(&self) -> Option<&E> {
241 match &self.0 {
242 WelpRepr::Sourced { source, .. } => source.downcast_ref::<E>(),
243 WelpRepr::Traced { .. } => None,
244 }
245 }
246
247 #[must_use]
251 #[inline]
252 pub fn downcast_mut<E: StdError + 'static>(&mut self) -> Option<&mut E> {
253 match &mut self.0 {
254 WelpRepr::Sourced { source, .. } => source.downcast_mut::<E>(),
255 WelpRepr::Traced { .. } => None,
256 }
257 }
258
259 pub fn downcast<E: StdError + 'static>(self) -> Result<E, Self> {
279 match self.0 {
280 WelpRepr::Sourced {
281 message,
282 source,
283 traces,
284 location,
285 } => match source.downcast::<E>() {
286 Ok(source) => Ok(*source),
287 Err(source) => Err(Self(WelpRepr::Sourced {
288 message,
289 source,
290 traces,
291 location,
292 })),
293 },
294 traced @ WelpRepr::Traced { .. } => Err(Self(traced)),
295 }
296 }
297}
298
299impl fmt::Debug for Welp {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let mut dbg = f.debug_struct("Welp");
302 if let Some(message) = self.message() {
303 dbg.field("message", &message);
304 }
305 match &self.0 {
306 WelpRepr::Sourced { source, .. } => {
307 dbg.field("source", source);
308 }
309 WelpRepr::Traced { .. } => {}
310 }
311 dbg.finish()
312 }
313}
314
315impl fmt::Display for Welp {
316 #[inline]
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 match &self.0 {
319 WelpRepr::Sourced {
322 message: None,
323 source,
324 ..
325 } => source.fmt(f),
326 WelpRepr::Sourced {
327 message: Some(message),
328 ..
329 }
330 | WelpRepr::Traced { message, .. } => f.write_str(message),
331 }
332 }
333}
334
335impl StdError for Welp {
336 fn source(&self) -> Option<&(dyn StdError + 'static)> {
337 match &self.0 {
338 WelpRepr::Sourced { source, .. } => Some(&**source),
339 WelpRepr::Traced { .. } => None,
340 }
341 }
342
343 #[cfg(feature = "unstable-error-generic-member-access")]
344 fn provide<'a>(&'a self, request: &mut core::error::Request<'a>) {
345 let traces = match &self.0 {
346 WelpRepr::Sourced { source, traces, .. } => {
347 source.provide(request);
352 traces
353 }
354 WelpRepr::Traced { traces, .. } => traces,
355 };
356 if traces.0.is_captured() {
357 request.provide_ref::<Backtrace>(&traces.0);
358 }
359 if traces.1.is_captured() {
360 request.provide_ref::<SpanTrace>(&traces.1);
361 }
362 }
363}
364
365impl Diagnostic for Welp {
366 fn oopsie_backtrace(&self) -> Option<&Backtrace> {
367 match &self.0 {
368 WelpRepr::Sourced { source, traces, .. } => {
369 crate::__private::source_backtrace(&**source).or(Some(&traces.0))
370 }
371 WelpRepr::Traced { traces, .. } => Some(&traces.0),
372 }
373 }
374
375 fn oopsie_spantrace(&self) -> Option<&SpanTrace> {
376 match &self.0 {
377 WelpRepr::Sourced { source, traces, .. } => {
378 crate::__private::source_spantrace(&**source).or(Some(&traces.1))
379 }
380 WelpRepr::Traced { traces, .. } => Some(&traces.1),
381 }
382 }
383
384 fn oopsie_location(&self) -> Option<&'static std::panic::Location<'static>> {
385 match &self.0 {
386 WelpRepr::Sourced { location, .. } | WelpRepr::Traced { location, .. } => {
387 Some(location)
388 }
389 }
390 }
391
392 fn oopsie_exit_code(&self) -> Option<core::num::NonZeroU8> {
393 match &self.0 {
398 WelpRepr::Sourced { source, .. } => crate::__private::source_exit_code(&**source),
399 WelpRepr::Traced { .. } => None,
400 }
401 }
402}
403
404pub trait WelpResultExt<T, E>: Sized {
418 fn welp(self) -> Result<T, Welp>;
426
427 fn welp_context(self, message: impl Into<String>) -> Result<T, Welp>;
437
438 fn with_welp_context<S, F>(self, f: F) -> Result<T, Welp>
442 where
443 S: Into<String>,
444 F: FnOnce(&E) -> S;
445}
446
447impl<T, E> WelpResultExt<T, E> for Result<T, E>
448where
449 E: StdError + Send + Sync + 'static,
450{
451 #[inline]
452 #[track_caller]
453 fn welp(self) -> Result<T, Welp> {
454 match self {
457 Ok(value) => Ok(value),
458 Err(e) => Err(Welp::from_error(e)),
459 }
460 }
461
462 #[inline]
463 #[track_caller]
464 fn welp_context(self, message: impl Into<String>) -> Result<T, Welp> {
465 match self {
468 Ok(value) => Ok(value),
469 Err(e) => Err(Welp::wrap(e, message)),
470 }
471 }
472
473 #[inline]
474 #[track_caller]
475 fn with_welp_context<S, F>(self, f: F) -> Result<T, Welp>
476 where
477 S: Into<String>,
478 F: FnOnce(&E) -> S,
479 {
480 match self {
481 Ok(value) => Ok(value),
482 Err(e) => {
483 let msg = f(&e);
484 Err(Welp::wrap(e, msg))
485 }
486 }
487 }
488}
489
490pub trait WelpOptionExt<T>: Sized {
503 fn welp_context(self, message: impl Into<String>) -> Result<T, Welp>;
513
514 fn with_welp_context<S, F>(self, f: F) -> Result<T, Welp>
517 where
518 S: Into<String>,
519 F: FnOnce() -> S;
520}
521
522impl<T> WelpOptionExt<T> for Option<T> {
523 #[inline]
524 #[track_caller]
525 fn welp_context(self, message: impl Into<String>) -> Result<T, Welp> {
526 match self {
529 Some(value) => Ok(value),
530 None => Err(Welp::new(message)),
531 }
532 }
533
534 #[inline]
535 #[track_caller]
536 fn with_welp_context<S, F>(self, f: F) -> Result<T, Welp>
537 where
538 S: Into<String>,
539 F: FnOnce() -> S,
540 {
541 match self {
542 Some(value) => Ok(value),
543 None => Err(Welp::new(f())),
544 }
545 }
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551 use crate::{RustBacktrace, with_rust_backtrace_override};
552
553 #[cfg(feature = "tracing")]
554 fn with_error_subscriber<R>(f: impl FnOnce() -> R) -> R {
555 use tracing_subscriber::prelude::*;
556 let subscriber = tracing_subscriber::registry().with(tracing_error::ErrorLayer::default());
557 tracing::subscriber::with_default(subscriber, f)
558 }
559
560 #[test]
561 fn new_captures_message() {
562 let err = Welp::new("hello");
563 assert_eq!(err.message(), Some("hello"));
564 assert_eq!(err.to_string(), "hello");
565 }
566
567 #[test]
568 fn new_accepts_string_and_str() {
569 let _: Welp = Welp::new("static");
570 let _: Welp = Welp::new(String::from("owned"));
571 let _: Welp = Welp::new(format!("formatted {}", 1));
572 }
573
574 #[test]
575 fn new_captures_traces() {
576 let err = Welp::new("oops");
577 assert!(err.oopsie_backtrace().is_some());
578 assert!(err.oopsie_spantrace().is_some());
579 }
580
581 #[test]
582 fn new_has_no_source() {
583 let err = Welp::new("oops");
584 assert!(StdError::source(&err).is_none());
585 }
586
587 #[test]
588 fn captures_location_at_call_site() {
589 let new_line = line!() + 1;
590 let traced = Welp::new("solo");
591 let loc = traced.oopsie_location().expect("traced location");
592 assert!(loc.file().ends_with("welp.rs"));
593 assert_eq!(loc.line(), new_line);
594
595 let wrap_line = line!() + 1;
596 let sourced = Welp::wrap(std::io::Error::other("x"), "outer");
597 let loc = sourced.oopsie_location().expect("sourced location");
598 assert_eq!(loc.line(), wrap_line);
599 }
600
601 #[test]
602 fn wrap_carries_source() {
603 let inner = std::io::Error::other("disk full");
604 let err = Welp::wrap(inner, "could not write");
605 assert_eq!(err.to_string(), "could not write");
606 let source = StdError::source(&err).expect("source missing");
607 assert_eq!(source.to_string(), "disk full");
608 }
609
610 #[test]
611 fn wrap_captures_traces_at_wrap_site() {
612 let err = Welp::wrap(std::io::Error::other("x"), "msg");
613 assert!(err.oopsie_backtrace().is_some());
614 assert!(err.oopsie_spantrace().is_some());
615 }
616
617 #[test]
618 fn wrap_boxed_carries_source() {
619 let boxed: BoxError = Box::new(std::io::Error::other("disk full"));
620 let err = Welp::wrap_boxed(boxed, "could not write");
621 assert_eq!(err.to_string(), "could not write");
622 assert_eq!(
623 StdError::source(&err).expect("source missing").to_string(),
624 "disk full"
625 );
626 }
627
628 #[test]
629 fn wrap_boxed_captures_location_at_call_site() {
630 let boxed: BoxError = Box::new(std::io::Error::other("x"));
631 let wrap_line = line!() + 1;
632 let err = Welp::wrap_boxed(boxed, "outer");
633 let loc = err.oopsie_location().expect("wrap_boxed location");
634 assert!(loc.file().ends_with("welp.rs"));
635 assert_eq!(loc.line(), wrap_line);
636 }
637
638 #[test]
639 fn wrap_boxed_captures_traces_at_wrap_site() {
640 let boxed: BoxError = Box::new(std::io::Error::other("x"));
641 let err = Welp::wrap_boxed(boxed, "msg");
642 assert!(err.oopsie_backtrace().is_some());
643 assert!(err.oopsie_spantrace().is_some());
644 }
645
646 #[test]
647 fn from_error_delegates_display_to_source() {
648 let err = Welp::from_error(std::io::Error::other("disk full"));
649 assert_eq!(err.to_string(), "disk full");
650 assert!(err.message().is_none());
651 let source = StdError::source(&err).expect("source missing");
652 assert_eq!(source.to_string(), "disk full");
653 }
654
655 #[test]
656 fn from_error_source_downcasts_to_original() {
657 let err = Welp::from_error(std::io::Error::new(std::io::ErrorKind::NotFound, "missing"));
658 let io = StdError::source(&err)
659 .and_then(|s| s.downcast_ref::<std::io::Error>())
660 .expect("source is the original io::Error");
661 assert_eq!(io.kind(), std::io::ErrorKind::NotFound);
662 }
663
664 #[test]
665 fn welp_on_ok_is_passthrough() {
666 let r: Result<i32, std::io::Error> = Ok(42);
667 assert_eq!(r.welp().unwrap(), 42);
668 }
669
670 #[test]
671 fn welp_on_err_delegates_display_and_keeps_source() {
672 let r: Result<i32, std::io::Error> = Err(std::io::Error::other("io fail"));
673 let err = r.welp().unwrap_err();
674 assert_eq!(err.to_string(), "io fail");
675 assert!(err.message().is_none());
676 assert_eq!(StdError::source(&err).unwrap().to_string(), "io fail");
677 }
678
679 #[test]
680 fn welp_captures_location_at_call_site() {
681 let call_line = line!() + 2;
682 let r: Result<i32, std::io::Error> = Err(std::io::Error::other("x"));
683 let err = r.welp().unwrap_err();
684 let loc = err.oopsie_location().expect("welp location");
685 assert!(loc.file().ends_with("welp.rs"));
686 assert_eq!(loc.line(), call_line);
687 }
688
689 #[cfg(feature = "tracing")]
690 #[test]
691 fn welp_extracts_origin_trace_from_diagnostic_source() {
692 let outer = with_rust_backtrace_override(RustBacktrace::Enabled, || {
696 with_error_subscriber(|| {
697 let _g = tracing::info_span!("origin").entered();
698 let inner: Result<(), Welp> = Err(Welp::new("inner"));
699 inner.welp().unwrap_err()
700 })
701 });
702 let bt = outer.oopsie_backtrace().expect("backtrace");
703 assert!(!bt.frames().is_empty());
704 #[cfg(feature = "unstable-error-generic-member-access")]
705 {
706 let source = StdError::source(&outer)
707 .and_then(|s| s.downcast_ref::<Welp>())
708 .expect("source is the inner Welp");
709 let inner_bt = source.oopsie_backtrace().expect("inner backtrace");
710 assert!(
711 std::ptr::eq(
712 std::ptr::from_ref::<Backtrace>(bt),
713 std::ptr::from_ref::<Backtrace>(inner_bt)
714 ),
715 "outer should surface the source's (origin-most) backtrace"
716 );
717 }
718 }
719
720 #[test]
721 fn welp_context_on_ok_is_passthrough() {
722 let r: Result<i32, std::io::Error> = Ok(42);
723 let mapped: Result<i32, Welp> = r.welp_context("nope");
724 assert_eq!(mapped.unwrap(), 42);
725 }
726
727 #[test]
728 fn welp_context_on_err_produces_sourced_welp() {
729 let r: Result<i32, std::io::Error> = Err(std::io::Error::other("io fail"));
730 let err = r.welp_context("read failed").unwrap_err();
731 assert_eq!(err.to_string(), "read failed");
732 assert_eq!(StdError::source(&err).unwrap().to_string(), "io fail");
733 }
734
735 #[test]
736 fn with_welp_context_does_not_run_closure_on_ok() {
737 let r: Result<i32, std::io::Error> = Ok(1);
738 let mapped: Result<i32, Welp> = r.with_welp_context(|_| -> String {
739 panic!("should not run");
740 });
741 mapped.unwrap();
742 }
743
744 #[test]
745 fn with_welp_context_runs_closure_on_err() {
746 let r: Result<i32, std::io::Error> = Err(std::io::Error::other("inner"));
747 let err = r
748 .with_welp_context(|e| format!("wrapped: {e}"))
749 .unwrap_err();
750 assert_eq!(err.to_string(), "wrapped: inner");
751 }
752
753 #[test]
754 fn option_welp_context_on_some_is_passthrough() {
755 let opt: Option<i32> = Some(7);
756 assert_eq!(opt.welp_context("missing").unwrap(), 7);
757 }
758
759 #[test]
760 fn option_welp_context_on_none_produces_traced_welp() {
761 let opt: Option<i32> = None;
762 let err = opt.welp_context("missing").unwrap_err();
763 assert_eq!(err.to_string(), "missing");
764 assert!(err.oopsie_backtrace().is_some());
765 assert!(StdError::source(&err).is_none());
766 }
767
768 #[test]
769 fn option_with_welp_context_does_not_run_closure_on_some() {
770 let opt: Option<i32> = Some(1);
771 let mapped: Result<i32, Welp> =
772 opt.with_welp_context(|| -> String { panic!("should not run") });
773 mapped.unwrap();
774 }
775
776 #[test]
777 fn option_with_welp_context_runs_closure_on_none() {
778 let opt: Option<i32> = None;
779 let err = opt
780 .with_welp_context(|| format!("missing key {}", "host"))
781 .unwrap_err();
782 assert_eq!(err.to_string(), "missing key host");
783 }
784
785 #[test]
786 fn chain_walks_welp_and_its_sources_self_first() {
787 use crate::ErrorChainExt as _;
788
789 let err = Welp::wrap(
790 Welp::wrap(std::io::Error::other("disk full"), "mid"),
791 "outer",
792 );
793 let messages: Vec<String> = err.chain().map(ToString::to_string).collect();
794 assert_eq!(messages, ["outer", "mid", "disk full"]);
795 assert_eq!(err.root_cause().to_string(), "disk full");
796 }
797
798 #[test]
799 fn downcast_ref_hits_matching_direct_source() {
800 let err = Welp::wrap(std::io::Error::other("disk full"), "outer");
801 let io = err.downcast_ref::<std::io::Error>().expect("source is io");
802 assert_eq!(io.to_string(), "disk full");
803 }
804
805 #[test]
806 fn downcast_ref_misses_on_wrong_type_and_traced() {
807 let sourced = Welp::wrap(std::io::Error::other("x"), "outer");
808 assert!(sourced.downcast_ref::<std::fmt::Error>().is_none());
809
810 let traced = Welp::new("solo");
811 assert!(traced.downcast_ref::<std::io::Error>().is_none());
812 }
813
814 #[test]
815 fn downcast_mut_borrows_matching_source() {
816 let mut err = Welp::wrap(std::io::Error::other("x"), "outer");
817 assert!(err.downcast_mut::<std::io::Error>().is_some());
818 assert!(err.downcast_mut::<std::fmt::Error>().is_none());
819 }
820
821 #[test]
822 fn downcast_recovers_matching_source() {
823 let err = Welp::wrap(
824 std::io::Error::new(std::io::ErrorKind::NotFound, "missing"),
825 "outer",
826 );
827 let io: std::io::Error = err.downcast().expect("source is io");
828 assert_eq!(io.kind(), std::io::ErrorKind::NotFound);
829 }
830
831 #[test]
832 fn downcast_only_targets_the_direct_source() {
833 let err = Welp::wrap(Welp::wrap(std::io::Error::other("x"), "mid"), "outer");
836 let err = err
837 .downcast::<std::io::Error>()
838 .expect_err("io is not the direct source");
839 assert!(err.downcast_ref::<Welp>().is_some());
840 }
841
842 #[test]
843 fn downcast_err_preserves_message_location_and_traces() {
844 let line = line!() + 1;
845 let err = Welp::wrap(std::io::Error::other("inner"), "outer");
846 let recovered = err
847 .downcast::<std::fmt::Error>()
848 .expect_err("wrong target type");
849 assert_eq!(recovered.message(), Some("outer"));
850 assert_eq!(recovered.to_string(), "outer");
851 let loc = recovered.oopsie_location().expect("location preserved");
852 assert_eq!(loc.line(), line);
853 assert!(recovered.oopsie_backtrace().is_some());
854 assert_eq!(StdError::source(&recovered).unwrap().to_string(), "inner");
856 }
857
858 #[test]
859 fn downcast_on_traced_returns_self() {
860 let err = Welp::new("solo");
861 let recovered = err
862 .downcast::<std::io::Error>()
863 .expect_err("traced has no source");
864 assert_eq!(recovered.message(), Some("solo"));
865 }
866
867 #[test]
868 fn debug_includes_source_for_sourced() {
869 let err = Welp::wrap(std::io::Error::other("inner"), "outer");
870 let dbg = format!("{err:?}");
871 assert!(dbg.contains("outer"));
872 assert!(dbg.contains("inner"));
873 }
874
875 #[test]
876 fn debug_omits_source_for_traced() {
877 let err = Welp::new("solo");
878 let dbg = format!("{err:?}");
879 assert!(dbg.contains("solo"));
880 assert!(!dbg.contains("source"));
881 }
882
883 #[cfg(all(feature = "unstable-error-generic-member-access", feature = "tracing"))]
884 #[test]
885 fn traced_provides_traces_via_request_ref() {
886 let err = with_rust_backtrace_override(RustBacktrace::Enabled, || {
888 with_error_subscriber(|| {
889 let _g = tracing::info_span!("solo").entered();
890 Welp::new("solo")
891 })
892 });
893 assert!(core::error::request_ref::<Backtrace>(&err).is_some());
894 assert!(core::error::request_ref::<SpanTrace>(&err).is_some());
895 }
896
897 #[cfg(feature = "unstable-error-generic-member-access")]
898 #[test]
899 fn empty_traces_are_not_provided() {
900 let err = with_rust_backtrace_override(RustBacktrace::Disabled, || Welp::new("solo"));
905 assert!(core::error::request_ref::<Backtrace>(&err).is_none());
906 assert!(core::error::request_ref::<SpanTrace>(&err).is_none());
907 }
908
909 #[cfg(feature = "tracing")]
910 #[test]
911 fn sourced_recovers_traces_from_diagnostic_source() {
912 let outer = with_rust_backtrace_override(RustBacktrace::Enabled, || {
916 with_error_subscriber(|| {
917 let _g = tracing::info_span!("origin").entered();
918 Welp::wrap(Welp::new("inner"), "outer")
919 })
920 });
921 let bt = outer.oopsie_backtrace().expect("backtrace");
922 let st = outer.oopsie_spantrace().expect("spantrace");
923 #[cfg(feature = "unstable-error-generic-member-access")]
926 {
927 let source = StdError::source(&outer)
928 .and_then(|s| s.downcast_ref::<Welp>())
929 .expect("source is the inner Welp");
930 let inner_bt = source.oopsie_backtrace().expect("inner backtrace");
931 let inner_st = source.oopsie_spantrace().expect("inner spantrace");
932 assert!(
933 std::ptr::eq(
934 std::ptr::from_ref::<Backtrace>(bt),
935 std::ptr::from_ref::<Backtrace>(inner_bt)
936 ),
937 "outer should surface the source's backtrace, not the wrap-site one"
938 );
939 assert!(
940 std::ptr::eq(
941 std::ptr::from_ref::<SpanTrace>(st),
942 std::ptr::from_ref::<SpanTrace>(inner_st)
943 ),
944 "outer should surface the source's spantrace, not the wrap-site one"
945 );
946 }
947 #[cfg(not(feature = "unstable-error-generic-member-access"))]
948 {
949 let _ = (bt, st);
950 }
951 }
952
953 #[cfg(all(feature = "unstable-error-generic-member-access", feature = "tracing"))]
954 #[test]
955 fn sourced_forwards_source_provide() {
956 let outer = with_rust_backtrace_override(RustBacktrace::Enabled, || {
958 with_error_subscriber(|| {
959 let _g = tracing::info_span!("origin").entered();
960 Welp::wrap(Welp::new("inner"), "outer")
961 })
962 });
963 assert!(core::error::request_ref::<Backtrace>(&outer).is_some());
964 assert!(core::error::request_ref::<SpanTrace>(&outer).is_some());
965 }
966
967 #[test]
968 fn sourced_falls_back_to_own_traces_for_foreign_source() {
969 let outer = Welp::wrap(std::io::Error::other("x"), "outer");
970 assert!(outer.oopsie_backtrace().is_some());
971 assert!(outer.oopsie_spantrace().is_some());
972 }
973
974 #[cfg(feature = "tracing")]
975 #[test]
976 fn empty_source_spantrace_does_not_shadow_wrap_site_capture() {
977 let inner = Welp::new("inner"); assert!(!inner.oopsie_spantrace().unwrap().is_captured());
979 let outer = with_error_subscriber(|| {
980 let _g = tracing::info_span!("wrap_site").entered();
981 Welp::wrap(inner, "outer")
982 });
983 assert!(outer.oopsie_spantrace().unwrap().is_captured());
984 }
985
986 #[test]
987 fn empty_source_backtrace_does_not_shadow_wrap_site_capture() {
988 let inner = with_rust_backtrace_override(RustBacktrace::Disabled, || Welp::new("inner"));
989 let outer =
990 with_rust_backtrace_override(RustBacktrace::Enabled, || Welp::wrap(inner, "outer"));
991 assert!(!outer.oopsie_backtrace().unwrap().frames().is_empty());
992 }
993
994 #[cfg(all(feature = "unstable-error-generic-member-access", feature = "tracing"))]
998 #[test]
999 fn wrap_surfaces_captured_source_spantrace() {
1000 let inner = with_error_subscriber(|| {
1001 let _g = tracing::info_span!("origin").entered();
1002 Welp::new("inner")
1003 });
1004 assert!(inner.oopsie_spantrace().unwrap().is_captured());
1005 let outer = Welp::wrap(inner, "outer"); assert!(outer.oopsie_spantrace().unwrap().is_captured());
1007 }
1008
1009 const _: () = {
1011 const fn assert_send<T: Send>() {}
1012 const fn assert_sync<T: Sync>() {}
1013 const fn assert_static<T: 'static>() {}
1014 assert_send::<Welp>();
1015 assert_sync::<Welp>();
1016 assert_static::<Welp>();
1017 };
1018
1019 #[test]
1020 fn welp_size_is_pinned() {
1021 assert_eq!(std::mem::size_of::<Welp>(), 48);
1027 }
1028}