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