#![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::{Contextual as _, Oopsie, ResultExt as _};
use std::error::Error as _;
use std::io;
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum MyError {
WithSource { source: io::Error, info: String },
Leaf { message: String },
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
struct StructWithSource {
source: io::Error,
detail: String,
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
struct StructNoSource {
reason: String,
}
#[test]
fn source_returns_some() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
let err: MyError = WithSource { info: "ctx" }.build_error(io_err);
let src = err.source();
assert!(src.is_some());
assert_eq!(src.unwrap().to_string(), "pipe broke");
}
#[test]
fn source_returns_none_leaf() {
let err = Leaf { message: "oops" }.build();
assert!(err.source().is_none());
}
#[test]
fn struct_source_some() {
let io_err = io::Error::new(io::ErrorKind::AddrInUse, "in use");
let err: StructWithSource = StructWithSourceOopsie { detail: "binding" }.build_error(io_err);
let src = err.source();
assert!(src.is_some());
assert_eq!(src.unwrap().to_string(), "in use");
}
#[test]
fn struct_source_none() {
let err = StructNoSourceOopsie {
reason: "just because",
}
.build();
assert!(err.source().is_none());
}
#[test]
fn error_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<MyError>();
assert_send_sync::<StructWithSource>();
assert_send_sync::<StructNoSource>();
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum ProvideError {
#[oopsie("bt error")]
#[oopsie(provide(ref, oopsie::Backtrace => bt.as_ref()))]
WithBacktrace {
msg: String,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
},
#[oopsie("help error")]
#[oopsie(provide(::oopsie::HelpText => ::oopsie::HelpText::from_static("try rebooting")))]
WithHelp { msg: String },
#[oopsie("coded error")]
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from("app::parse")))]
WithCode { msg: String },
#[oopsie("field help error")]
#[oopsie(provide(::oopsie::HelpText => ::oopsie::HelpText::from(msg.clone())))]
WithFieldHelp { msg: String },
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_backtrace_ref() {
let err = WithBacktrace { msg: "boom" }.build();
let bt = core::error::request_ref::<oopsie::Backtrace>(&err);
assert!(bt.is_some(), "should provide a Backtrace ref");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_help_text() {
let err = WithHelp { msg: "broken" }.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(help.is_some(), "should provide HelpText");
assert_eq!(&*help.unwrap(), "try rebooting");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_error_code() {
let err = WithCode { msg: "bad input" }.build();
let code = core::error::request_value::<oopsie::ErrorCode>(&err);
assert!(code.is_some(), "should provide ErrorCode");
assert_eq!(&*code.unwrap(), "app::parse");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_help_text_from_user_field() {
let err = WithFieldHelp {
msg: "needs attention",
}
.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(
help.is_some(),
"should provide HelpText built from a user field"
);
assert_eq!(&*help.unwrap(), "needs attention");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum InnerError {
#[oopsie("inner: {detail}")]
#[oopsie(provide(ref, oopsie::Backtrace => bt.as_ref()))]
#[oopsie(provide(::oopsie::HelpText => ::oopsie::HelpText::from_static("fix the inner thing")))]
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from("inner::code")))]
Root {
detail: String,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
},
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum MiddleError {
#[oopsie("middle: {context}")]
Wrapped { source: InnerError, context: String },
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum OuterError {
#[oopsie("outer: {label}")]
Top { source: MiddleError, label: String },
}
#[test]
fn nested_source_chain_two_levels() {
let inner = Root {
detail: "db failed",
}
.build();
let middle: MiddleError = Wrapped { context: "query" }.build_error(inner);
let outer: OuterError = Top { label: "request" }.build_error(middle);
assert_eq!(outer.to_string(), "outer: request");
let mid = outer.source().expect("outer should have source");
assert_eq!(mid.to_string(), "middle: query");
let inn = mid.source().expect("middle should have source");
assert_eq!(inn.to_string(), "inner: db failed");
assert!(inn.source().is_none(), "inner is a leaf error");
}
#[test]
fn nested_source_chain_via_context() {
let inner = Root { detail: "timeout" }.build();
let result: Result<(), InnerError> = Err(inner);
let middle: Result<(), MiddleError> = result.context(Wrapped {
context: "connecting",
});
let result2: Result<(), OuterError> = middle.context(Top { label: "service" });
let outer = result2.unwrap_err();
assert_eq!(outer.to_string(), "outer: service");
let mid = outer.source().unwrap();
assert_eq!(mid.to_string(), "middle: connecting");
let inn = mid.source().unwrap();
assert_eq!(inn.to_string(), "inner: timeout");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn nested_backtrace_propagated_through_source_chain() {
let inner = Root { detail: "crash" }.build();
let middle: MiddleError = Wrapped {
context: "processing",
}
.build_error(inner);
let outer: OuterError = Top { label: "handler" }.build_error(middle);
let inner_ref = outer.source().unwrap().source().unwrap();
let bt_direct = core::error::request_ref::<oopsie::Backtrace>(inner_ref);
assert!(
bt_direct.is_some(),
"inner error should provide backtrace directly"
);
let bt_outer = core::error::request_ref::<oopsie::Backtrace>(&outer);
assert!(
bt_outer.is_some(),
"backtrace should propagate through source chain to outer"
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn nested_help_text_propagated_through_source_chain() {
let inner = Root { detail: "issue" }.build();
let middle: MiddleError = Wrapped {
context: "handling",
}
.build_error(inner);
let outer: OuterError = Top { label: "api" }.build_error(middle);
let help = core::error::request_value::<oopsie::HelpText>(&outer);
assert!(
help.is_some(),
"HelpText should propagate through source chain"
);
assert_eq!(&*help.unwrap(), "fix the inner thing");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn nested_error_code_propagated_through_source_chain() {
let inner = Root {
detail: "parse fail",
}
.build();
let middle: MiddleError = Wrapped {
context: "decoding",
}
.build_error(inner);
let outer: OuterError = Top { label: "ingest" }.build_error(middle);
let code = core::error::request_value::<oopsie::ErrorCode>(&outer);
assert!(
code.is_some(),
"ErrorCode should propagate through source chain"
);
assert_eq!(&*code.unwrap(), "inner::code");
}
#[oopsie::oopsie(traced)]
pub enum AttrInnerError {
#[oopsie("attr inner: {msg}")]
Boom { msg: String },
}
#[oopsie::oopsie(traced)]
pub enum AttrOuterError {
#[oopsie("attr outer: {ctx}")]
Wrapper { source: AttrInnerError, ctx: String },
}
#[test]
fn nested_attr_macro_source_chain() {
use attr_inner_oopsies::Boom;
use attr_outer_oopsies::Wrapper;
let inner = Boom { msg: "exploded" }.build();
let outer: AttrOuterError = Wrapper { ctx: "defusing" }.build_error(inner);
assert_eq!(outer.to_string(), "attr outer: defusing");
let src = outer.source().unwrap();
assert_eq!(src.to_string(), "attr inner: exploded");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn nested_attr_macro_backtrace_propagated() {
use attr_inner_oopsies::Boom;
use attr_outer_oopsies::Wrapper;
oopsie_core::test_utils::force_backtrace();
let inner = Boom { msg: "kaboom" }.build();
let outer: AttrOuterError = Wrapper { ctx: "handling" }.build_error(inner);
let bt = core::error::request_ref::<oopsie::Backtrace>(&outer);
assert!(
bt.is_some(),
"backtrace from #[oopsie(traced)] inner should propagate to outer"
);
}
#[cfg(all(feature = "unstable-error-generic-member-access", feature = "tracing"))]
#[test]
fn stable_accessor_surfaces_deepest_trace() {
use attr_inner_oopsies::Boom;
use attr_outer_oopsies::Wrapper;
use oopsie::Diagnostic as _;
use tracing::instrument;
#[instrument(target = "test")]
fn make_inner() -> AttrInnerError {
attr_inner_oopsies::Boom { msg: "deep" }.build()
}
oopsie_core::test_utils::force_backtrace();
let _guard = oopsie_core::test_utils::init_test_subscriber();
let inner = make_inner();
let outer: AttrOuterError = Wrapper { ctx: "shallow" }.build_error(inner);
let bt_provide = core::error::request_ref::<oopsie::Backtrace>(&outer)
.expect("provider path yields a backtrace");
let st_provide = core::error::request_ref::<oopsie::SpanTrace>(&outer)
.expect("provider path yields a span trace");
let bt_stable = outer
.oopsie_backtrace()
.expect("stable accessor yields a backtrace");
let st_stable = outer
.oopsie_spantrace()
.expect("stable accessor yields a span trace");
assert!(
std::ptr::eq(bt_provide, bt_stable),
"oopsie_backtrace() must surface the same deepest backtrace as the provider API, \
not the outer wrapper's wrap-site one"
);
assert!(
std::ptr::eq(st_provide, st_stable),
"oopsie_spantrace() must surface the same deepest span trace as the provider API"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BareHelpError {
#[oopsie(display("need help"), help = "Try rebooting")]
NeedHelp { detail: String },
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_help_text_bare_derive() {
let err = NeedHelp { detail: "stuck" }.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(help.is_some(), "should provide HelpText");
assert_eq!(&*help.unwrap(), "Try rebooting");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BareCodeError {
#[oopsie(display("coded error"), code = "bare::code")]
Coded { msg: String },
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_error_code_bare_derive() {
let err = Coded { msg: "fail" }.build();
let code = core::error::request_value::<oopsie::ErrorCode>(&err);
assert!(code.is_some(), "should provide ErrorCode");
assert_eq!(&*code.unwrap(), "bare::code");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BareHelpCodeError {
#[oopsie(display("both"), help = "help text", code = "app::both")]
Both { info: String },
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn provide_help_and_code_combined() {
let err = Both { info: "combo" }.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(help.is_some(), "should provide HelpText");
assert_eq!(&*help.unwrap(), "help text");
let code = core::error::request_value::<oopsie::ErrorCode>(&err);
assert!(code.is_some(), "should provide ErrorCode");
assert_eq!(&*code.unwrap(), "app::both");
}
#[oopsie::oopsie(traced)]
pub struct AttrStructWithBt {
msg: String,
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn struct_provide_backtrace() {
oopsie_core::test_utils::force_backtrace();
let err = AttrStructWithBtOopsie { msg: "test" }.build();
let bt = core::error::request_ref::<oopsie::Backtrace>(&err);
assert!(
bt.is_some(),
"struct provide() should correctly provide backtrace"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum DynamicHelpError {
#[oopsie("validation failed")]
WithDynamicHelp {
#[oopsie(help)]
suggestion: String,
},
#[oopsie("other error")]
NoHelp { msg: String },
}
#[test]
fn dynamic_help_field_returns_value() {
use oopsie::Diagnostic as _;
let err = WithDynamicHelp {
suggestion: "try a shorter name",
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "should return dynamic help text");
assert_eq!(&*help.unwrap(), "try a shorter name");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn dynamic_help_field_provider_matches_accessor() {
use oopsie::Diagnostic as _;
let err = WithDynamicHelp {
suggestion: "try a shorter name",
}
.build();
let provided = core::error::request_value::<oopsie::HelpText>(&err);
assert!(provided.is_some(), "provide() should yield dynamic help");
assert_eq!(provided, err.oopsie_help_text());
}
#[test]
fn no_help_field_returns_none() {
use oopsie::Diagnostic as _;
let err = NoHelp { msg: "boom" }.build();
assert!(
err.oopsie_help_text().is_none(),
"variant without help should return None"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
struct StructDynamicHelp {
#[oopsie(help)]
suggestion: String,
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn struct_dynamic_help_field_provider_matches_accessor() {
use oopsie::Diagnostic as _;
let err = StructDynamicHelpOopsie {
suggestion: "try a shorter name".to_owned(),
}
.build();
let provided = core::error::request_value::<oopsie::HelpText>(&err);
assert!(provided.is_some(), "provide() should yield dynamic help");
assert_eq!(provided, err.oopsie_help_text());
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum HelpInterpError {
#[oopsie("connect failed")]
#[oopsie(help("Retry connecting to {}", host))]
SingleField { host: String },
#[oopsie("ingest failed")]
#[oopsie(help("got {} errors in {}", count, module))]
MultiField { count: u32, module: String },
}
#[test]
fn help_single_field_interpolation_renders_value() {
use oopsie::Diagnostic as _;
let err = SingleField {
host: "db.local".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "should render interpolated help text");
assert_eq!(&*help.unwrap(), "Retry connecting to db.local");
}
#[test]
fn help_multi_field_interpolation_renders_value() {
use oopsie::Diagnostic as _;
let err = MultiField {
count: 3u32,
module: "ingest".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "should render interpolated help text");
assert_eq!(&*help.unwrap(), "got 3 errors in ingest");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn help_field_interpolation_via_provider() {
let err = SingleField {
host: "db.local".to_owned(),
}
.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(
help.is_some(),
"provider path should yield interpolated help"
);
assert_eq!(&*help.unwrap(), "Retry connecting to db.local");
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix, help = "fix the file at {path}")]
struct StructInlineHelp {
path: String,
}
#[test]
fn struct_inline_capture_help_renders_field() {
use oopsie::Diagnostic as _;
let err = StructInlineHelpOopsie {
path: "/etc/hosts".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "inline-capture help should render");
assert_eq!(&*help.unwrap(), "fix the file at /etc/hosts");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum HelpInlineError {
#[oopsie("connect failed")]
#[oopsie(help("retry connecting to {host}"))]
ListForm { host: String },
#[oopsie("restart needed")]
#[oopsie(help = "restart {service}")]
NameValueForm { service: String },
#[oopsie("templating")]
#[oopsie(help = "wrap names in {{braces}}")]
EscapedOnly { unused: String },
}
#[test]
fn enum_inline_capture_help_list_form_renders_field() {
use oopsie::Diagnostic as _;
let err = ListForm {
host: "db.local".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "inline-capture help should render");
assert_eq!(&*help.unwrap(), "retry connecting to db.local");
}
#[test]
fn enum_inline_capture_help_name_value_form_renders_field() {
use oopsie::Diagnostic as _;
let err = NameValueForm {
service: "nginx".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "inline-capture help should render");
assert_eq!(&*help.unwrap(), "restart nginx");
}
#[test]
fn help_escaped_braces_unescape_on_static_path() {
use oopsie::Diagnostic as _;
let err = EscapedOnly {
unused: "x".to_owned(),
}
.build();
let help = err.oopsie_help_text();
assert!(help.is_some(), "escaped-brace help should render");
assert_eq!(&*help.unwrap(), "wrap names in {braces}");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn help_inline_capture_via_provider() {
let err = ListForm {
host: "db.local".to_owned(),
}
.build();
let help = core::error::request_value::<oopsie::HelpText>(&err);
assert!(
help.is_some(),
"provider path should render interpolated help"
);
assert_eq!(&*help.unwrap(), "retry connecting to db.local");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum CodeAccessorError {
#[oopsie("static coded")]
#[oopsie(code = "test::code")]
StaticCode { msg: String },
#[oopsie("dynamic coded")]
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from("dyn::code")))]
DynamicCode { msg: String },
#[oopsie("uncoded")]
NoCode { msg: String },
}
#[test]
fn error_code_accessor_returns_static_code() {
use oopsie::Diagnostic as _;
let err = StaticCode {
msg: "boom".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(code.is_some(), "static code should be accessible on stable");
assert_eq!(code.unwrap().as_str(), "test::code");
}
#[test]
fn error_code_accessor_returns_none_when_absent() {
use oopsie::Diagnostic as _;
let err = NoCode {
msg: "boom".to_owned(),
}
.build();
assert!(
err.oopsie_error_code().is_none(),
"variant without code should return None"
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn error_code_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = StaticCode {
msg: "boom".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(), via_provider.as_str());
assert_eq!(via_accessor.as_str(), "test::code");
}
#[test]
fn error_code_accessor_surfaces_dynamic_provide() {
use oopsie::Diagnostic as _;
let err = DynamicCode {
msg: "boom".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(
code.is_some(),
"dynamic provide(ErrorCode) reaches accessor"
);
assert_eq!(code.unwrap().as_str(), "dyn::code");
}
#[test]
fn error_code_renders_via_display_end_to_end() {
use oopsie::Diagnostic as _;
let err = StaticCode {
msg: "boom".to_owned(),
}
.build();
let code = err.oopsie_error_code().expect("code present");
assert_eq!(format!("{code}"), "test::code");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn error_code_dynamic_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = DynamicCode {
msg: "boom".to_owned(),
}
.build();
let via_accessor = err
.oopsie_error_code()
.expect("accessor yields dynamic code");
let via_provider =
core::error::request_value::<oopsie::ErrorCode>(&err).expect("provider yields code");
assert_eq!(via_accessor.as_str(), "dyn::code");
assert_eq!(via_accessor.as_str(), via_provider.as_str());
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
struct FieldCodeStruct {
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from("field::code")))]
msg: String,
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum FieldCodeEnum {
#[oopsie("coded by field")]
FieldCoded {
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from("variant::field::code")))]
msg: String,
},
}
#[test]
fn error_code_accessor_surfaces_field_level_provide_struct() {
use oopsie::Diagnostic as _;
let err = FieldCodeStructOopsie {
msg: "boom".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(
code.is_some(),
"field-level provide(ErrorCode) must reach the stable accessor"
);
assert_eq!(code.unwrap().as_str(), "field::code");
}
#[test]
fn error_code_accessor_surfaces_field_level_provide_enum() {
use oopsie::Diagnostic as _;
let err = FieldCoded {
msg: "boom".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(
code.is_some(),
"field-level provide(ErrorCode) on a variant field must reach the stable accessor"
);
assert_eq!(code.unwrap().as_str(), "variant::field::code");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn error_code_field_level_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = FieldCodeStructOopsie {
msg: "boom".to_owned(),
}
.build();
let via_accessor = err.oopsie_error_code().expect("accessor yields field code");
let via_provider =
core::error::request_value::<oopsie::ErrorCode>(&err).expect("provider yields code");
assert_eq!(via_accessor.as_str(), "field::code");
assert_eq!(via_accessor.as_str(), via_provider.as_str());
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum CodeInterpError {
#[oopsie("connect failed")]
#[oopsie(code("app::{}", kind))]
Positional { kind: String },
#[oopsie("ingest failed")]
#[oopsie(code = "svc::{module}")]
InlineCapture { module: String },
}
#[test]
fn code_positional_arg_interpolation_renders_value() {
use oopsie::Diagnostic as _;
let err = Positional {
kind: "db".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(code.is_some(), "should render interpolated code");
assert_eq!(code.unwrap().as_str(), "app::db");
}
#[test]
fn code_inline_capture_interpolation_renders_value() {
use oopsie::Diagnostic as _;
let err = InlineCapture {
module: "ingest".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(code.is_some(), "should render interpolated code");
assert_eq!(code.unwrap().as_str(), "svc::ingest");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn code_interpolation_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = Positional {
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(), via_provider.as_str());
assert_eq!(via_accessor.as_str(), "app::net");
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
#[oopsie("struct failed")]
#[oopsie(code = "svc::{kind}")]
struct StructCodeInterp {
kind: String,
}
#[test]
fn struct_code_inline_capture_renders_value() {
use oopsie::Diagnostic as _;
let err = StructCodeInterpOopsie {
kind: "fs".to_owned(),
}
.build();
let code = err.oopsie_error_code();
assert!(code.is_some(), "should render interpolated code");
assert_eq!(code.unwrap().as_str(), "svc::fs");
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn struct_code_inline_capture_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = StructCodeInterpOopsie {
kind: "mem".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(), via_provider.as_str());
assert_eq!(via_accessor.as_str(), "svc::mem");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum FieldCodeError {
#[oopsie("boom")]
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from(format!("app::{kind}"))))]
FieldCode { kind: String },
#[oopsie("ref boom")]
#[oopsie(provide(ref, ::oopsie::ErrorCode => code))]
RefCode { code: oopsie::ErrorCode },
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
#[oopsie("struct boom")]
#[oopsie(provide(::oopsie::ErrorCode => ::oopsie::ErrorCode::from(format!("app::{kind}"))))]
struct StructFieldCode {
kind: String,
}
#[derive(Debug, Oopsie)]
#[oopsie(suffix)]
#[oopsie("struct ref boom")]
#[oopsie(provide(ref, ::oopsie::ErrorCode => code))]
struct StructRefCode {
code: oopsie::ErrorCode,
}
#[test]
fn error_code_provide_can_reference_fields() {
use oopsie::Diagnostic as _;
let err = FieldCode {
kind: "db".to_owned(),
}
.build();
let code = err.oopsie_error_code().expect("accessor yields code");
assert_eq!(code.as_str(), "app::db");
}
#[test]
fn error_code_ref_provide_returns_owned_clone() {
use oopsie::Diagnostic as _;
let err = RefCode {
code: oopsie::ErrorCode::from("ref::code"),
}
.build();
let code = err.oopsie_error_code().expect("accessor yields code");
assert_eq!(code.as_str(), "ref::code");
}
#[test]
fn struct_error_code_provide_can_reference_fields() {
use oopsie::Diagnostic as _;
let err = StructFieldCodeOopsie {
kind: "fs".to_owned(),
}
.build();
let code = err.oopsie_error_code().expect("accessor yields code");
assert_eq!(code.as_str(), "app::fs");
}
#[test]
fn struct_error_code_ref_provide_returns_owned_clone() {
use oopsie::Diagnostic as _;
let err = StructRefCodeOopsie {
code: oopsie::ErrorCode::from("ref::struct"),
}
.build();
let code = err.oopsie_error_code().expect("accessor yields code");
assert_eq!(code.as_str(), "ref::struct");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false), exit_code = 3)]
enum ExitCodeError {
#[oopsie("default coded")]
UsesDefault { msg: String },
#[oopsie("override coded")]
#[oopsie(exit_code = 78)]
Overrides { msg: String },
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum NoContainerExitError {
#[oopsie("only this one")]
#[oopsie(exit_code = 42)]
SoloCoded { msg: String },
#[oopsie("uncoded")]
Uncoded { msg: String },
}
#[test]
fn exit_code_variant_value_surfaces() {
use oopsie::Diagnostic as _;
let err = SoloCoded {
msg: "boom".to_owned(),
}
.build();
assert_eq!(
err.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(42)
);
}
#[test]
fn exit_code_absent_returns_none() {
use oopsie::Diagnostic as _;
let err = Uncoded {
msg: "boom".to_owned(),
}
.build();
assert!(err.oopsie_exit_code().is_none());
}
#[test]
fn exit_code_container_default_applies() {
use oopsie::Diagnostic as _;
let err = UsesDefault {
msg: "boom".to_owned(),
}
.build();
assert_eq!(
err.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(3)
);
}
#[test]
fn exit_code_variant_overrides_container_default() {
use oopsie::Diagnostic as _;
let err = Overrides {
msg: "boom".to_owned(),
}
.build();
assert_eq!(
err.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(78)
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn exit_code_accessor_and_provider_agree() {
use oopsie::Diagnostic as _;
let err = Overrides {
msg: "boom".to_owned(),
}
.build();
let via_accessor = err.oopsie_exit_code().expect("accessor yields exit code");
let via_provider = core::error::request_value::<core::num::NonZeroU8>(&err)
.expect("provider yields exit code");
assert_eq!(via_accessor, via_provider);
assert_eq!(via_accessor.get(), 78);
}
#[oopsie::oopsie]
#[oopsie(exit_code = 70)]
pub struct ExitStructError {
detail: String,
}
#[test]
fn struct_exit_code_surfaces() {
use oopsie::Diagnostic as _;
let err = ExitStructOopsie {
detail: "x".to_owned(),
}
.build();
assert_eq!(
err.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(70)
);
}
#[oopsie::oopsie]
#[oopsie(exit_code = 65)]
pub enum ExitLeafError {
#[oopsie("leaf boom")]
Boom { detail: String },
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum ExitWrapperError {
#[oopsie("wrapped")]
#[oopsie(transparent)]
Wrap { source: ExitLeafError },
}
#[test]
fn exit_code_transparent_forwards_from_source() {
use oopsie::Diagnostic as _;
let leaf = exit_leaf_oopsies::Boom {
detail: "x".to_owned(),
}
.build();
let wrapper = ExitWrapperError::Wrap { source: leaf };
assert_eq!(
wrapper.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(65)
);
}
#[cfg(feature = "unstable-error-generic-member-access")]
#[test]
fn welp_forwards_source_exit_code() {
use oopsie::Diagnostic as _;
use oopsie::WelpResultExt as _;
let leaf = exit_leaf_oopsies::Boom {
detail: "x".to_owned(),
}
.build();
let welp = Err::<(), _>(leaf).welp().unwrap_err();
assert_eq!(
welp.oopsie_exit_code().map(core::num::NonZeroU8::get),
Some(65)
);
}