#![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::{Diagnostic as _, Oopsie};
use std::error::Error as _;
use std::io;
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum TransparentError {
#[oopsie(display("io error"), transparent)]
Io { source: io::Error },
}
#[test]
fn transparent_generates_from() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
let err: TransparentError = TransparentError::from(io_err);
assert!(matches!(err, TransparentError::Io { .. }));
}
#[test]
fn transparent_display() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
let err: TransparentError = TransparentError::from(io_err);
assert_eq!(err.to_string(), "io error");
}
#[test]
fn transparent_source_delegates_to_inner() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
let err: TransparentError = TransparentError::from(io_err);
let src = err
.source()
.expect("transparent variant exposes its source");
assert_eq!(src.to_string(), "pipe broke");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum TracedError {
#[oopsie(display("traced io error"), transparent)]
TracedIo {
source: io::Error,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
},
}
#[test]
fn transparent_with_auto_fields() {
oopsie::backtrace::set_override(oopsie::RustBacktrace::Enabled);
let io_err = io::Error::new(io::ErrorKind::Other, "something");
let err: TracedError = TracedError::from(io_err);
assert_eq!(err.to_string(), "traced io error");
let TracedError::TracedIo { bt, .. } = &err;
assert!(
!bt.frames().is_empty(),
"auto-captured backtrace must be populated"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum TransformError {
#[oopsie(display("transformed io"), transparent)]
Wrapped {
#[oopsie(from(io::Error, |e| Box::new(e)))]
source: Box<io::Error>,
},
}
#[test]
fn transparent_variant_with_transform() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "nope");
let err: TransformError = TransformError::from(io_err);
let TransformError::Wrapped { source } = &err;
assert_eq!(source.kind(), io::ErrorKind::PermissionDenied);
assert_eq!(err.to_string(), "transformed io");
assert_eq!(err.source().expect("has source").to_string(), "nope");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum TransformCaptureError {
#[oopsie(display("transform+capture"), transparent)]
Wrapped {
#[oopsie(from(io::Error, |e| Box::new(e)))]
source: Box<io::Error>,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
},
}
#[test]
fn transparent_transform_with_auto_field() {
oopsie::backtrace::set_override(oopsie::RustBacktrace::Enabled);
let io_err = io::Error::new(io::ErrorKind::TimedOut, "slow");
let err: TransformCaptureError = TransformCaptureError::from(io_err);
let TransformCaptureError::Wrapped { source, bt } = &err;
assert_eq!(source.kind(), io::ErrorKind::TimedOut);
assert!(
!bt.frames().is_empty(),
"auto field captured despite transform"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BoxedSourceError {
#[oopsie(display("boxed io"), transparent)]
Io { source: Box<io::Error> },
}
#[test]
fn transparent_auto_boxed_source() {
let io_err = io::Error::new(io::ErrorKind::ConnectionReset, "reset");
let err: BoxedSourceError = BoxedSourceError::from(io_err);
let BoxedSourceError::Io { source } = &err;
assert_eq!(source.kind(), io::ErrorKind::ConnectionReset);
assert_eq!(err.to_string(), "boxed io");
assert_eq!(err.source().expect("has source").to_string(), "reset");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum MixedError {
#[oopsie(display("wrapped io"), transparent)]
Wrapped { source: io::Error },
#[oopsie("custom: {msg}")]
Custom { msg: String },
}
#[test]
fn mixed_transparent_and_regular() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "missing");
let err: MixedError = MixedError::from(io_err);
assert!(matches!(err, MixedError::Wrapped { .. }));
assert_eq!(err.to_string(), "wrapped io");
let err = Custom { msg: "bad thing" }.build();
assert!(matches!(err, MixedError::Custom { .. }));
assert_eq!(err.to_string(), "custom: bad thing");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
#[oopsie(display("wrapped io"), transparent)]
struct TransparentStruct {
source: io::Error,
}
#[test]
fn transparent_struct_from_and_display() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
let err: TransparentStruct = TransparentStruct::from(io_err);
assert_eq!(err.to_string(), "wrapped io");
assert_eq!(
err.source().expect("struct exposes source").to_string(),
"pipe broke"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
#[oopsie(display("transformed struct"), transparent)]
struct TransformStruct {
#[oopsie(from(io::Error, |e| Box::new(e)))]
source: Box<io::Error>,
}
#[test]
fn transparent_struct_with_transform() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "nope");
let err: TransformStruct = TransformStruct::from(io_err);
assert_eq!(err.source.kind(), io::ErrorKind::PermissionDenied);
assert_eq!(err.to_string(), "transformed struct");
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
#[oopsie(display("traced struct"), transparent)]
struct TracedStruct {
source: io::Error,
#[oopsie(capture)]
bt: Box<oopsie::Backtrace>,
}
#[test]
fn transparent_struct_with_auto_field() {
oopsie::backtrace::set_override(oopsie::RustBacktrace::Enabled);
let io_err = io::Error::new(io::ErrorKind::Other, "boom");
let err: TracedStruct = TracedStruct::from(io_err);
assert_eq!(err.to_string(), "traced struct");
assert!(
!err.bt.frames().is_empty(),
"struct auto backtrace must be captured"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum DiagLeaf {
#[oopsie(
display("leaf failed: {what}"),
code = "leaf::failed",
help = "turn it off and on again"
)]
Failed { what: String },
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BareWrapper {
#[oopsie(transparent)]
Leaf { source: DiagLeaf },
}
#[test]
fn bare_transparent_delegates_display_and_forwards_diagnostic() {
let leaf = Failed { what: "disk" }.build();
let wrapped: BareWrapper = BareWrapper::from(leaf);
assert_eq!(wrapped.to_string(), "leaf failed: disk");
assert_eq!(
wrapped.oopsie_error_code().expect("code forwards").as_str(),
"leaf::failed"
);
assert_eq!(
wrapped.oopsie_help_text().expect("help forwards").as_str(),
"turn it off and on again"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
#[oopsie(transparent)]
struct BareStruct {
source: DiagLeaf,
}
#[test]
fn bare_transparent_struct_delegates_and_forwards() {
let leaf = Failed { what: "net" }.build();
let wrapped: BareStruct = BareStruct::from(leaf);
assert_eq!(wrapped.to_string(), "leaf failed: net");
assert_eq!(
wrapped.oopsie_error_code().expect("code forwards").as_str(),
"leaf::failed"
);
assert_eq!(
wrapped.oopsie_help_text().expect("help forwards").as_str(),
"turn it off and on again"
);
}
#[derive(Debug, Oopsie)]
#[oopsie(module(false))]
enum BareIoWrapper {
#[oopsie(transparent)]
Io { source: io::Error },
}
#[test]
fn bare_transparent_non_diagnostic_source_forwards_none() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "missing file");
let wrapped: BareIoWrapper = BareIoWrapper::from(io_err);
assert_eq!(wrapped.to_string(), "missing file");
assert!(wrapped.oopsie_error_code().is_none());
assert!(wrapped.oopsie_help_text().is_none());
}
#[cfg(all(feature = "unstable-error-generic-member-access", feature = "tracing"))]
mod traced_transparent {
use oopsie::Diagnostic as _;
#[oopsie::oopsie(traced)]
pub enum Leaf {
#[oopsie("leaf boom: {detail}")]
Boom { detail: String },
}
#[oopsie::oopsie(traced)]
pub enum Wrapper {
#[oopsie(display("wrapper around leaf"), transparent)]
Around { source: Leaf },
}
#[test]
fn transparent_traced_stable_matches_provider_deepest() {
use tracing::instrument;
#[instrument(target = "test")]
fn make_leaf() -> Leaf {
leaf_oopsies::Boom { detail: "x" }.build()
}
oopsie_core::test_utils::force_backtrace();
let _guard = oopsie_core::test_utils::init_test_subscriber();
let leaf = make_leaf();
let outer: Wrapper = Wrapper::from(leaf);
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),
"transparent traced wrapper: stable oopsie_backtrace() must surface the same \
deepest backtrace as the provider API"
);
assert!(
std::ptr::eq(st_provide, st_stable),
"transparent traced wrapper: stable oopsie_spantrace() must surface the same \
deepest span trace as the provider API"
);
}
}
mod traced_transparent_code {
use oopsie::Diagnostic as _;
#[oopsie::oopsie(traced)]
pub enum Leaf {
#[oopsie("leaf boom")]
#[oopsie(code = "leaf::real_code")]
Boom,
}
#[oopsie::oopsie(traced)]
pub enum Wrapper {
#[oopsie(transparent)]
Around { source: Leaf },
}
#[test]
fn traced_transparent_forwards_source_code_not_autocode() {
use leaf_oopsies::Boom;
let leaf = Boom.build();
let outer: Wrapper = Wrapper::from(leaf);
assert_eq!(
outer
.oopsie_error_code()
.expect("transparent forwards the leaf's code")
.as_str(),
"leaf::real_code",
"transparent must forward the source's code, not the traced wrapper's auto-code"
);
assert_eq!(outer.to_string(), "leaf boom");
}
}