#![cfg_attr(
feature = "unstable-error-generic-member-access",
feature(error_generic_member_access)
)]
#![allow(
unused,
clippy::all,
reason = "derive-macro test fixtures intentionally trip style lints"
)]
use oopsie::oopsie;
#[oopsie(traced)]
pub enum AppError {
#[oopsie("conn failed: {addr}")]
ConnFailed { addr: String },
}
#[test]
fn traced_enum_basic() {
let err = app_oopsies::ConnFailed { addr: "127.0.0.1" }.build();
assert!(matches!(&err, AppError::ConnFailed { addr, .. } if addr == "127.0.0.1"));
assert_eq!(err.to_string(), "conn failed: 127.0.0.1");
}
#[oopsie(traced)]
pub struct ConnError {
reason: String,
}
#[test]
fn traced_struct_basic() {
let err = ConnOopsie { reason: "refused" }.build();
assert_eq!(err.reason, "refused");
}
#[oopsie(traced)]
pub enum InjectError {
#[oopsie("injected")]
Injected { info: String },
}
#[test]
fn traced_injects_backtrace() {
let err = inject_oopsies::Injected { info: "test" }.build();
assert!(matches!(&err, InjectError::Injected { info, .. } if info == "test"));
}
#[oopsie(traced)]
pub enum HelpCodeError {
#[oopsie(
display("connection refused"),
help = "Check network",
code = "net::conn_refused"
)]
Refused { target: String },
}
#[test]
fn traced_with_help_and_code() {
let err = help_code_oopsies::Refused { target: "db" }.build();
assert!(matches!(&err, HelpCodeError::Refused { target, .. } if target == "db"));
#[cfg(feature = "unstable-error-generic-member-access")]
{
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(help.is_some());
let code = core::error::request_value::<oopsie::ErrorCode>(&err);
assert!(code.is_some());
}
}
#[oopsie(traced(backtrace(false), spantrace(false), location(false)))]
pub enum DiscriminantUnit {
#[oopsie("a")]
A = 1,
}
#[test]
fn unit_variant_with_discriminant_compiles() {
let err = discriminant_unit_oopsies::A.build();
assert!(matches!(err, DiscriminantUnit::A));
assert_eq!(DiscriminantUnit::A as u8, 1);
}
#[oopsie(traced)]
pub enum FooBarError {
#[oopsie("foo")]
Foo,
}
#[oopsie(traced)]
pub enum MyError {
#[oopsie("my")]
My,
}
#[test]
fn traced_enum_module_naming() {
let _ = foo_bar_oopsies::Foo.build();
let _ = my_oopsies::My.build();
}
#[oopsie(traced)]
pub enum PreExistingBtError {
#[oopsie("has backtrace")]
#[oopsie(provide(ref, oopsie::Backtrace => bt.as_ref()))]
HasBt {
msg: String,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
},
}
#[test]
fn traced_does_not_duplicate_backtrace() {
let err = pre_existing_bt_oopsies::HasBt { msg: "test" }.build();
assert_eq!(err.to_string(), "has backtrace");
}
#[oopsie(traced)]
pub enum PreExistingStError {
#[oopsie("has spantrace")]
#[oopsie(provide(ref, oopsie::SpanTrace => st.as_ref()))]
HasSt {
msg: String,
#[oopsie(capture)]
st: Box<oopsie::SpanTrace>,
},
}
#[test]
fn traced_does_not_duplicate_spantrace() {
let err = pre_existing_st_oopsies::HasSt { msg: "test" }.build();
assert_eq!(err.to_string(), "has spantrace");
}
#[oopsie(traced)]
pub struct PreExistingBtStructError {
msg: String,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
}
#[test]
fn traced_struct_does_not_duplicate_backtrace() {
let err = PreExistingBtStructOopsie { msg: "struct bt" }.build();
assert_eq!(err.msg, "struct bt");
}
#[oopsie(traced(spantrace(false)))]
pub enum BacktraceOnlyError {
#[oopsie("bt only")]
BtOnly { msg: String },
}
#[test]
fn traced_explicit_backtrace_only() {
use oopsie::Diagnostic as _;
let err = backtrace_only_oopsies::BtOnly { msg: "test" }.build();
assert_eq!(err.to_string(), "bt only");
assert!(
err.oopsie_backtrace().is_some(),
"backtrace stays on when only spantrace is disabled"
);
assert!(
err.oopsie_spantrace().is_none(),
"`spantrace(false)` must NOT inject a spantrace"
);
}
#[oopsie(traced(backtrace(false)))]
pub enum SpantraceOnlyError {
#[oopsie("st only")]
StOnly { msg: String },
}
#[test]
fn traced_explicit_spantrace_only() {
let err = spantrace_only_oopsies::StOnly { msg: "test" }.build();
assert_eq!(err.to_string(), "st only");
}
#[oopsie(traced(code = false))]
pub enum NoCodeError {
#[oopsie("no code")]
NoCode { msg: String },
}
#[test]
fn traced_code_disabled() {
let err = no_code_oopsies::NoCode { msg: "test" }.build();
assert_eq!(err.to_string(), "no code");
#[cfg(feature = "unstable-error-generic-member-access")]
{
let code = core::error::request_value::<oopsie::ErrorCode>(&err);
assert!(
code.is_none(),
"ErrorCode should not be provided when code=false"
);
}
}
#[oopsie(traced)]
pub enum VariantToggleError {
#[oopsie(traced = false)]
#[oopsie("off: {msg}")]
Off { msg: String },
#[oopsie("on: {msg}")]
On { msg: String },
}
#[test]
fn traced_variant_toggle_controls_injection_per_variant() {
use oopsie::Diagnostic as _;
let off = variant_toggle_oopsies::Off {
msg: "x".to_owned(),
}
.build();
assert_eq!(off.to_string(), "off: x");
assert!(
off.oopsie_backtrace().is_none(),
"variant traced=false must skip backtrace injection"
);
assert!(
off.oopsie_spantrace().is_none(),
"variant traced=false must skip spantrace injection"
);
let on = variant_toggle_oopsies::On {
msg: "x".to_owned(),
}
.build();
assert_eq!(on.to_string(), "on: x");
assert!(
on.oopsie_backtrace().is_some(),
"variant without override inherits enum traced behavior"
);
assert!(
on.oopsie_spantrace().is_some(),
"variant without override inherits enum traced behavior"
);
}
#[oopsie(traced)]
pub enum DiscriminantVariantOptOutError {
#[oopsie(traced = false)]
#[oopsie("a")]
A = 1,
}
#[test]
fn traced_variant_opt_out_allows_explicit_discriminant() {
let err = discriminant_variant_opt_out_oopsies::A.build();
assert!(matches!(err, DiscriminantVariantOptOutError::A));
}
use oopsie::Diagnostic as _;
#[oopsie(traced)]
pub enum DefaultPackedError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
#[oopsie(traced(boxed = false))]
pub enum PackedInlineError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
#[oopsie(traced(packed = false))]
pub enum SeparateBoxedError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
#[oopsie(traced(packed = false, boxed = false))]
pub enum SeparateInlineError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
#[oopsie(traced(packed = false, spantrace(boxed = false)))]
pub enum MixedError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
#[oopsie(traced(spantrace(false)))]
pub enum SingleBacktraceError {
#[oopsie("boom: {info}")]
Boom { info: String },
}
fn assert_both_traces<E: oopsie::Diagnostic>(e: &E) {
assert!(e.oopsie_backtrace().is_some(), "backtrace accessor missing");
assert!(e.oopsie_spantrace().is_some(), "spantrace accessor missing");
}
#[test]
fn layout_default_packed_exposes_both_traces() {
let e = default_packed_oopsies::Boom { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_packed_inline_exposes_both_traces() {
let e = packed_inline_oopsies::Boom { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_separate_boxed_exposes_both_traces() {
let e = separate_boxed_oopsies::Boom { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_separate_inline_exposes_both_traces() {
let e = separate_inline_oopsies::Boom { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_mixed_exposes_both_traces() {
let e = mixed_oopsies::Boom { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_single_trace_fallback_backtrace_only() {
let e = single_backtrace_oopsies::Boom { info: "x" }.build();
assert!(e.oopsie_backtrace().is_some());
assert!(e.oopsie_spantrace().is_none());
}
#[cfg(not(feature = "tracing"))]
#[test]
fn featureless_spantrace_degrades_to_uncaptured() {
use oopsie::Diagnostic as _;
oopsie::backtrace::set_override(oopsie::RustBacktrace::Enabled);
let e = default_packed_oopsies::Boom { info: "x" }.build();
assert!(
!e.oopsie_spantrace()
.expect("spantrace accessor present")
.is_captured(),
"featureless spantrace must report nothing captured"
);
assert!(
e.oopsie_backtrace().is_some(),
"backtrace accessor must capture in the featureless layout"
);
oopsie::backtrace::clear_override();
}
#[cfg(not(feature = "tracing"))]
#[oopsie(traced(spantrace))]
pub struct FeaturelessSpantraceError {
info: String,
}
#[cfg(not(feature = "tracing"))]
#[test]
fn featureless_explicit_spantrace_compiles_and_degrades() {
use oopsie::Diagnostic as _;
let e = FeaturelessSpantraceOopsie { info: "x" }.build();
assert!(
!e.oopsie_spantrace()
.expect("spantrace accessor present")
.is_captured()
);
}
#[oopsie(traced)]
pub struct PackedStructError {
info: String,
}
#[oopsie(traced(packed = false, boxed = false))]
pub struct InlineStructError {
info: String,
}
#[test]
fn layout_struct_default_packed_exposes_both_traces() {
let e = PackedStructOopsie { info: "x" }.build();
assert_both_traces(&e);
}
#[test]
fn layout_struct_separate_inline_exposes_both_traces() {
let e = InlineStructOopsie { info: "x" }.build();
assert_both_traces(&e);
}
#[oopsie(traced)]
pub struct WrongTypedBacktraceError {
backtrace: String,
info: String,
}
#[cfg(feature = "tracing")]
#[test]
fn wrong_typed_backtrace_field_is_ordinary_and_real_backtrace_injected() {
let e = WrongTypedBacktraceOopsie {
backtrace: "external textual backtrace",
info: "x",
}
.build();
assert_both_traces(&e);
assert_eq!(e.backtrace, "external textual backtrace");
}
#[oopsie(traced(timestamp))]
pub enum TimestampError {
#[oopsie("ts: {info}")]
Boom { info: String },
}
#[test]
fn timestamp_nested_injects_systemtime_field() {
let before = std::time::SystemTime::now();
let e = timestamp_oopsies::Boom {
info: "x".to_owned(),
}
.build();
assert_eq!(e.to_string(), "ts: x");
let TimestampError::Boom {
__oopsie_timestamp, ..
} = &e;
let stored: &std::time::SystemTime = __oopsie_timestamp;
assert!(*stored >= before);
}
#[cfg(feature = "tracing")]
#[oopsie(traced(backtrace(false), spantrace(false), timestamp))]
pub enum BareTimestampError {
#[oopsie("bare ts")]
Boom { info: String },
}
#[cfg(feature = "tracing")]
#[test]
fn timestamp_is_auto_captured_and_absent_from_selector() {
use oopsie::Diagnostic as _;
let before = std::time::SystemTime::now();
let e = bare_timestamp_oopsies::Boom {
info: "x".to_owned(),
}
.build();
let BareTimestampError::Boom {
__oopsie_timestamp, ..
} = &e;
assert!(*__oopsie_timestamp >= before);
assert!(
e.oopsie_backtrace().is_none(),
"backtrace(false) must not inject a backtrace"
);
assert!(
e.oopsie_spantrace().is_none(),
"spantrace(false) must not inject a spantrace"
);
}
#[oopsie]
pub enum TransparentInnerError {
#[oopsie("inner")]
Inner,
}
#[oopsie(traced(timestamp))]
pub enum TransparentTimestampError {
#[oopsie(transparent)]
Wrapped { source: TransparentInnerError },
}
#[test]
fn timestamp_composes_with_transparent() {
let before = std::time::SystemTime::now();
let outer: TransparentTimestampError = transparent_inner_oopsies::Inner.build().into();
let TransparentTimestampError::Wrapped {
__oopsie_timestamp, ..
} = &outer;
let _: &std::time::SystemTime = __oopsie_timestamp;
assert!(*__oopsie_timestamp >= before);
}
#[cfg(feature = "chrono")]
mod chrono_timestamp {
use oopsie::oopsie;
#[oopsie(traced(timestamp(chrono = true)))]
pub enum ChronoTimestampError {
#[oopsie("chrono ts")]
Boom { info: String },
}
#[test]
fn timestamp_chrono_flag_swaps_field_type_to_datetime_local() {
let before = chrono::Local::now();
let e = chrono_timestamp_oopsies::Boom {
info: "x".to_owned(),
}
.build();
assert_eq!(e.to_string(), "chrono ts");
let ChronoTimestampError::Boom {
__oopsie_timestamp, ..
} = &e;
let stored: &chrono::DateTime<chrono::Local> = __oopsie_timestamp;
assert!(*stored >= before);
}
}
#[oopsie(traced(timestamp(provide = true)))]
pub enum ProvidedTimestampError {
#[oopsie("provided ts")]
Boom { info: String },
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn timestamp_provide_surfaces_via_provider_api() {
let before = std::time::SystemTime::now();
let e = provided_timestamp_oopsies::Boom {
info: "x".to_owned(),
}
.build();
let got = core::error::request_value::<std::time::SystemTime>(&e);
let ts = got.expect("timestamp must be queryable via the provider API");
assert!(ts >= before);
}
#[cfg(feature = "tracing")]
mod custom_trace {
use std::borrow::Borrow;
#[derive(Debug)]
#[expect(
unnameable_types,
reason = "pub fixture type in a private module: referenced by the generated pub Diagnostic accessor but not nameable at pub"
)]
pub struct Backtrace {
pub inner: oopsie::Backtrace,
pub tag: u32,
}
impl oopsie::Capturable for Backtrace {
fn capture() -> Self {
Self {
inner: oopsie::Capturable::capture(),
tag: 0xC0FFEE,
}
}
}
impl Borrow<oopsie::Backtrace> for Backtrace {
fn borrow(&self) -> &oopsie::Backtrace {
&self.inner
}
}
}
#[cfg(feature = "tracing")]
#[oopsie(traced(
packed = false,
backtrace(r#type = custom_trace::Backtrace, boxed = false),
spantrace(boxed = false)
))]
pub enum CustomBacktraceError {
#[oopsie("custom bt: {info}")]
Boom { info: String },
}
#[cfg(feature = "tracing")]
#[test]
fn custom_backtrace_type_is_injected_and_captured() {
use oopsie::Diagnostic as _;
let e = custom_backtrace_oopsies::Boom {
info: "x".to_owned(),
}
.build();
let CustomBacktraceError::Boom {
__oopsie_backtrace, ..
} = &e;
let bt: &custom_trace::Backtrace = __oopsie_backtrace;
assert_eq!(bt.tag, 0xC0FFEE);
assert!(e.oopsie_backtrace().is_some());
assert!(e.oopsie_spantrace().is_some());
}
#[cfg(feature = "chrono")]
#[test]
fn chrono_timestamp_compiles_without_direct_dep_path() {
#[oopsie(traced(timestamp(chrono = true)))]
#[oopsie(module(false))]
pub enum ChronoStamp {
#[oopsie("boom")]
Boom,
}
let e = Boom.build();
assert!(format!("{e:?}").contains("__oopsie_timestamp"));
}
#[test]
fn wrongly_typed_timestamp_named_field_does_not_suppress_injection() {
#[oopsie(traced(timestamp))]
#[oopsie(module(false), suffix)]
pub struct TsNamed {
timestamp: u64,
}
let e = TsNamedOopsie { timestamp: 5u64 }.build();
assert!(format!("{e:?}").contains("__oopsie_timestamp"));
}
#[oopsie(traced)]
#[oopsie(module(false))]
pub enum ListCodeError {
#[oopsie("conn failed")]
#[oopsie(code("app::{}", kind))]
Conn { kind: String },
}
#[test]
fn list_form_code_suppresses_auto_code() {
use oopsie::Diagnostic as _;
let err = Conn {
kind: "db".to_owned(),
}
.build();
let code = err
.oopsie_error_code()
.expect("list-form code should be present");
assert_eq!(code.as_str(), "app::db");
assert!(
!code.as_str().contains("ListCodeError"),
"auto-code leaked: {}",
code.as_str()
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn list_form_code_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = Conn {
kind: "net".to_owned(),
}
.build();
let via_accessor = err.oopsie_error_code().expect("accessor yields code");
let via_provider =
core::error::request_value::<oopsie::ErrorCode>(&err).expect("provider yields code");
assert_eq!(via_accessor.as_str(), "app::net");
assert_eq!(via_accessor.as_str(), via_provider.as_str());
}
#[test]
#[expect(
non_camel_case_types,
reason = "the raw-identifier variant under test is intentionally keyword-shaped"
)]
fn raw_ident_variant_auto_code_drops_prefix() {
use oopsie::Diagnostic as _;
#[oopsie(traced)]
#[oopsie(module(false))]
enum RawCodeError {
#[oopsie("raw")]
r#type { info: String },
}
let err = r#type {
info: "x".to_owned(),
}
.build();
let code = err
.oopsie_error_code()
.expect("auto-code should be present");
assert!(
code.as_str().ends_with("::RawCodeError::type"),
"auto-code leaked raw prefix: {}",
code.as_str()
);
assert!(
!code.as_str().contains("r#"),
"auto-code leaked raw prefix: {}",
code.as_str()
);
}