use std::error::Error as _;
use fack::prelude::*;
#[derive(Debug, Error)]
#[error("inner failure")]
struct Inner;
#[derive(Debug, Error)]
#[error("outer failure")]
#[error(source(inner))]
struct StructSource {
inner: Inner,
}
#[derive(Debug, Error)]
enum EnumSource {
#[error("named failure")]
#[error(source(inner))]
Named { inner: Inner, _code: u8 },
#[error("tuple failure")]
#[error(source(0))]
Tuple(Inner, ()),
}
#[test]
fn structure_source_is_selected_field() {
let error = StructSource { inner: Inner };
let source = error.source().expect("declared source must exist");
assert_eq!(source.to_string(), "inner failure");
}
#[test]
fn enum_sources_are_selected_bindings() {
let named = EnumSource::Named { inner: Inner, _code: 7 };
let tuple = EnumSource::Tuple(Inner, ());
let named_source = named.source().expect("named source must exist");
let tuple_source = tuple.source().expect("tuple source must exist");
assert_eq!(named_source.to_string(), "inner failure");
assert_eq!(tuple_source.to_string(), "inner failure");
}
#[derive(Debug, Error)]
#[error("from struct")]
#[error(from)]
struct StructFrom(Inner);
#[derive(Debug, Error)]
enum EnumFrom {
#[error("from enum")]
#[error(from)]
Inner(Inner),
}
#[test]
fn from_implies_the_converted_field_is_the_source() {
let structure = StructFrom::from(Inner);
let enumeration = EnumFrom::from(Inner);
assert_eq!(structure.source().expect("from source must exist").to_string(), "inner failure");
assert_eq!(enumeration.source().expect("from source must exist").to_string(), "inner failure");
}
#[derive(Debug, Error)]
#[error(transparent(0))]
struct Transparent(Inner);
#[test]
fn transparent_forwards_display_and_inner_source() {
let error = Transparent(Inner);
assert_eq!(error.to_string(), "inner failure");
assert!(error.source().is_none());
}
#[derive(Debug, Error)]
#[error("tuple {0} debug {1:?} hex {2:#x}")]
struct TupleFormat(u8, &'static str, u16);
#[derive(Debug, Error)]
#[error("named {name} debug {value:?} hex {code:#x}")]
struct NamedFormat {
name: &'static str,
value: Option<u8>,
code: u16,
}
#[test]
fn format_strings_capture_tuple_and_named_fields() {
let tuple = TupleFormat(7, "text", 0x2a);
let named = NamedFormat {
name: "item",
value: Some(3),
code: 0x2a,
};
assert_eq!(tuple.to_string(), "tuple 7 debug \"text\" hex 0x2a");
assert_eq!(named.to_string(), "named item debug Some(3) hex 0x2a");
}
#[derive(Debug, Error)]
#[error("escaped {{field}} and {}", value.abs())]
struct ExpressionFormat {
value: i8,
}
#[test]
fn format_strings_preserve_explicit_arguments_and_escaped_braces() {
let error = ExpressionFormat { value: -7 };
assert_eq!(error.to_string(), "escaped {field} and 7");
}
#[derive(Debug, Error)]
#[error("generic {0}")]
struct GenericDisplay<T>(T);
#[derive(Debug, Error)]
#[error("generic debug {0:?}")]
struct GenericDebug<T>(T);
#[test]
fn format_captures_add_only_required_format_bounds() {
let display = GenericDisplay(17_u8);
let debug = GenericDebug(Some(4_u8));
assert_eq!(display.to_string(), "generic 17");
assert_eq!(debug.to_string(), "generic debug Some(4)");
}
#[derive(Debug, Error)]
#[error("boxed source")]
#[error(source(inner))]
struct BoxedSource {
inner: Box<Inner>,
}
#[derive(Debug, Error)]
#[error("optional source")]
#[error(source(inner))]
struct OptionalSource {
inner: Option<Inner>,
}
#[derive(Debug, Error)]
#[error("optional boxed source")]
#[error(source(inner))]
struct OptionalBoxedSource {
inner: Option<Box<Inner>>,
}
#[derive(Debug, Error)]
#[error("dynamic source")]
#[error(source(inner))]
struct DynamicSource {
inner: Box<dyn std::error::Error + Send + Sync>,
}
#[test]
fn common_source_container_shapes_are_supported() {
let boxed = BoxedSource { inner: Box::new(Inner) };
let optional = OptionalSource { inner: Some(Inner) };
let absent = OptionalSource { inner: None };
let optional_boxed = OptionalBoxedSource {
inner: Some(Box::new(Inner)),
};
let dynamic = DynamicSource { inner: Box::new(Inner) };
assert_eq!(boxed.source().expect("boxed source").to_string(), "inner failure");
assert_eq!(optional.source().expect("optional source").to_string(), "inner failure");
assert!(absent.source().is_none());
assert_eq!(optional_boxed.source().expect("optional boxed source").to_string(), "inner failure");
assert_eq!(dynamic.source().expect("dynamic source").to_string(), "inner failure");
}
#[derive(Debug, Error)]
#[error("named argument {rendered}", rendered = value.abs())]
struct NamedArgument {
value: i8,
}
#[test]
fn explicit_named_format_arguments_take_precedence_over_field_capture() {
let error = NamedArgument { value: -9 };
assert_eq!(error.to_string(), "named argument 9");
}
fn custom_format(error: &CustomDisplay, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let CustomDisplay { value } = error;
write!(formatter, "custom {value}")
}
#[derive(Debug, Error)]
#[error(display(custom_format))]
struct CustomDisplay {
value: u8,
}
#[test]
fn custom_display_formatter_is_an_escape_hatch() {
assert_eq!(CustomDisplay { value: 5 }.to_string(), "custom 5");
}
#[derive(Debug)]
struct DebugOnly;
#[derive(Debug, Error)]
#[error("selected {selected}")]
struct SelectiveFormat<T, U> {
selected: T,
_unused: U,
}
#[test]
fn unused_fields_do_not_acquire_format_bounds() {
let error = SelectiveFormat {
selected: 3_u8,
_unused: DebugOnly,
};
assert_eq!(error.to_string(), "selected 3");
}
#[derive(Debug, Error)]
#[error("named {value:>width$}")]
struct NamedWidth {
value: u8,
width: usize,
}
#[derive(Debug, Error)]
#[error("tuple {0:>1$}")]
struct TupleWidth(u8, usize);
#[test]
fn dynamic_width_fields_are_resolved_and_bound() {
assert_eq!(NamedWidth { value: 7, width: 3 }.to_string(), "named 7");
assert_eq!(TupleWidth(7, 3).to_string(), "tuple 7");
}
#[derive(Debug, Error)]
#[error("generic from")]
#[error(from)]
struct GenericFrom<E>(E);
#[test]
fn generic_from_adds_the_source_bound_to_only_the_error_impl() {
let error = GenericFrom::from(Inner);
assert_eq!(error.source().expect("generic source").to_string(), "inner failure");
}
fn format_custom_enum(error: &CustomEnum, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match error {
CustomEnum::Value(value) => write!(formatter, "enum custom {value}"),
}
}
#[derive(Debug, Error)]
enum CustomEnum {
#[error(display(format_custom_enum))]
Value(u8),
}
#[test]
fn custom_display_formatter_works_for_enum_variants() {
assert_eq!(CustomEnum::Value(8).to_string(), "enum custom 8");
}