diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c0658..9bb6c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. ### Added - _Nothing yet._ +## [0.5.12] - 2025-10-04 + +### Tests +- Added runtime assertions covering every derive formatter variant and + validating lowercase versus uppercase rendering differences during error + formatting. +- Expanded the formatter `trybuild` suite with per-formatter success cases and + new compile-fail fixtures for unsupported uppercase specifiers to guarantee + diagnostics remain descriptive. + ## [0.5.11] - 2025-10-03 ### Changed diff --git a/Cargo.lock b/Cargo.lock index 8591afc..22f7026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,7 +1527,7 @@ dependencies = [ [[package]] name = "masterror" -version = "0.5.11" +version = "0.5.12" dependencies = [ "actix-web", "axum", diff --git a/Cargo.toml b/Cargo.toml index 9266200..06bccb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.5.11" +version = "0.5.12" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index dd02fb8..e079b4e 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Stable categories, conservative HTTP mapping, no `unsafe`. ~~~toml [dependencies] -masterror = { version = "0.5.10", default-features = false } +masterror = { version = "0.5.12", default-features = false } # or with features: -# masterror = { version = "0.5.10", features = [ +# masterror = { version = "0.5.12", features = [ # "axum", "actix", "openapi", "serde_json", # "sqlx", "sqlx-migrate", "reqwest", "redis", # "validator", "config", "tokio", "multipart", @@ -66,10 +66,10 @@ masterror = { version = "0.5.10", default-features = false } ~~~toml [dependencies] # lean core -masterror = { version = "0.5.10", default-features = false } +masterror = { version = "0.5.12", default-features = false } # with Axum/Actix + JSON + integrations -# masterror = { version = "0.5.10", features = [ +# masterror = { version = "0.5.12", features = [ # "axum", "actix", "openapi", "serde_json", # "sqlx", "sqlx-migrate", "reqwest", "redis", # "validator", "config", "tokio", "multipart", @@ -349,13 +349,13 @@ assert_eq!(resp.status, 401); Minimal core: ~~~toml -masterror = { version = "0.5.10", default-features = false } +masterror = { version = "0.5.12", default-features = false } ~~~ API (Axum + JSON + deps): ~~~toml -masterror = { version = "0.5.10", features = [ +masterror = { version = "0.5.12", features = [ "axum", "serde_json", "openapi", "sqlx", "reqwest", "redis", "validator", "config", "tokio" ] } @@ -364,7 +364,7 @@ masterror = { version = "0.5.10", features = [ API (Actix + JSON + deps): ~~~toml -masterror = { version = "0.5.10", features = [ +masterror = { version = "0.5.12", features = [ "actix", "serde_json", "openapi", "sqlx", "reqwest", "redis", "validator", "config", "tokio" ] } diff --git a/tests/error_derive.rs b/tests/error_derive.rs index dbf488b..665a055 100644 --- a/tests/error_derive.rs +++ b/tests/error_derive.rs @@ -215,6 +215,60 @@ struct FormatterDebugShowcase { tuple: (&'static str, u8) } +#[derive(Debug, Error)] +#[error("{value}")] +struct DisplayFormatterError { + value: &'static str +} + +#[derive(Debug, Error)] +#[error("debug={value:?} #debug={value:#?}")] +struct DebugFormatterError { + value: PrettyDebugValue +} + +#[derive(Debug, Error)] +#[error("lower={value:x} #lower={value:#x}")] +struct LowerHexFormatterError { + value: u32 +} + +#[derive(Debug, Error)] +#[error("upper={value:X} #upper={value:#X}")] +struct UpperHexFormatterError { + value: u32 +} + +#[derive(Debug, Error)] +#[error("binary={value:b} #binary={value:#b}")] +struct BinaryFormatterError { + value: u16 +} + +#[derive(Debug, Error)] +#[error("octal={value:o} #octal={value:#o}")] +struct OctalFormatterError { + value: u16 +} + +#[derive(Debug, Error)] +#[error("pointer={value:p} #pointer={value:#p}")] +struct PointerFormatterError { + value: *const u32 +} + +#[derive(Debug, Error)] +#[error("lower={value:e} #lower={value:#e}")] +struct LowerExpFormatterError { + value: f64 +} + +#[derive(Debug, Error)] +#[error("upper={value:E} #upper={value:#E}")] +struct UpperExpFormatterError { + value: f64 +} + #[cfg(error_generic_member_access)] fn assert_backtrace_interfaces(error: &E, expected: &std::backtrace::Backtrace) where @@ -537,3 +591,94 @@ fn supports_extended_formatters() { assert_eq!(err.to_string(), expected); assert!(StdError::source(&err).is_none()); } + +#[test] +fn formatter_variants_render_expected_output() { + let display = DisplayFormatterError { + value: "display" + }; + assert_eq!(display.to_string(), "display"); + + let debug = DebugFormatterError { + value: PrettyDebugValue { + label: "Debug" + } + }; + let debug_expected = format!( + "debug={value:?} #debug={value:#?}", + value = PrettyDebugValue { + label: "Debug" + } + ); + assert_eq!(debug.to_string(), debug_expected); + assert_ne!( + format!( + "{value:?}", + value = PrettyDebugValue { + label: "Debug" + } + ), + format!( + "{value:#?}", + value = PrettyDebugValue { + label: "Debug" + } + ) + ); + + const HEX_VALUE: u32 = 0x5A5A; + let lower_hex = LowerHexFormatterError { + value: HEX_VALUE + }; + let lower_hex_expected = format!("lower={value:x} #lower={value:#x}", value = HEX_VALUE); + assert_eq!(lower_hex.to_string(), lower_hex_expected); + assert_ne!(format!("{HEX_VALUE:x}"), format!("{HEX_VALUE:#x}")); + + let upper_hex = UpperHexFormatterError { + value: HEX_VALUE + }; + let upper_hex_expected = format!("upper={value:X} #upper={value:#X}", value = HEX_VALUE); + assert_eq!(upper_hex.to_string(), upper_hex_expected); + assert_ne!(format!("{HEX_VALUE:X}"), format!("{HEX_VALUE:#X}")); + assert_ne!(format!("{HEX_VALUE:x}"), format!("{HEX_VALUE:X}")); + + const INTEGER_VALUE: u16 = 0b1010_1100; + let binary = BinaryFormatterError { + value: INTEGER_VALUE + }; + let binary_expected = format!("binary={value:b} #binary={value:#b}", value = INTEGER_VALUE); + assert_eq!(binary.to_string(), binary_expected); + assert_ne!(format!("{INTEGER_VALUE:b}"), format!("{INTEGER_VALUE:#b}")); + + let octal = OctalFormatterError { + value: INTEGER_VALUE + }; + let octal_expected = format!("octal={value:o} #octal={value:#o}", value = INTEGER_VALUE); + assert_eq!(octal.to_string(), octal_expected); + assert_ne!(format!("{INTEGER_VALUE:o}"), format!("{INTEGER_VALUE:#o}")); + + let pointer_value = core::ptr::null::(); + let pointer = PointerFormatterError { + value: pointer_value + }; + let pointer_expected = format!( + "pointer={value:p} #pointer={value:#p}", + value = pointer_value + ); + assert_eq!(pointer.to_string(), pointer_expected); + assert_ne!(format!("{pointer_value:p}"), format!("{pointer_value:#p}")); + + const FLOAT_VALUE: f64 = 1234.5; + let lower_exp = LowerExpFormatterError { + value: FLOAT_VALUE + }; + let lower_exp_expected = format!("lower={value:e} #lower={value:#e}", value = FLOAT_VALUE); + assert_eq!(lower_exp.to_string(), lower_exp_expected); + + let upper_exp = UpperExpFormatterError { + value: FLOAT_VALUE + }; + let upper_exp_expected = format!("upper={value:E} #upper={value:#E}", value = FLOAT_VALUE); + assert_eq!(upper_exp.to_string(), upper_exp_expected); + assert_ne!(format!("{FLOAT_VALUE:e}"), format!("{FLOAT_VALUE:E}")); +} diff --git a/tests/ui/formatter/fail/uppercase_binary.rs b/tests/ui/formatter/fail/uppercase_binary.rs new file mode 100644 index 0000000..902cd3e --- /dev/null +++ b/tests/ui/formatter/fail/uppercase_binary.rs @@ -0,0 +1,9 @@ +use masterror::Error; + +#[derive(Debug, Error)] +#[error("{value:B}")] +struct UppercaseBinary { + value: u8, +} + +fn main() {} diff --git a/tests/ui/formatter/fail/uppercase_binary.stderr b/tests/ui/formatter/fail/uppercase_binary.stderr new file mode 100644 index 0000000..f49c391 --- /dev/null +++ b/tests/ui/formatter/fail/uppercase_binary.stderr @@ -0,0 +1,11 @@ +error: placeholder spanning bytes 0..9 uses an unsupported formatter + --> tests/ui/formatter/fail/uppercase_binary.rs:4:9 + | +4 | #[error("{value:B}")] + | ^^^^^^^^^^^ + +error: missing #[error(...)] attribute + --> tests/ui/formatter/fail/uppercase_binary.rs:5:8 + | +5 | struct UppercaseBinary { + | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/formatter/fail/uppercase_pointer.rs b/tests/ui/formatter/fail/uppercase_pointer.rs new file mode 100644 index 0000000..4c36df8 --- /dev/null +++ b/tests/ui/formatter/fail/uppercase_pointer.rs @@ -0,0 +1,9 @@ +use masterror::Error; + +#[derive(Debug, Error)] +#[error("{value:P}")] +struct UppercasePointer { + value: *const u8, +} + +fn main() {} diff --git a/tests/ui/formatter/fail/uppercase_pointer.stderr b/tests/ui/formatter/fail/uppercase_pointer.stderr new file mode 100644 index 0000000..b87f76d --- /dev/null +++ b/tests/ui/formatter/fail/uppercase_pointer.stderr @@ -0,0 +1,11 @@ +error: placeholder spanning bytes 0..9 uses an unsupported formatter + --> tests/ui/formatter/fail/uppercase_pointer.rs:4:9 + | +4 | #[error("{value:P}")] + | ^^^^^^^^^^^ + +error: missing #[error(...)] attribute + --> tests/ui/formatter/fail/uppercase_pointer.rs:5:8 + | +5 | struct UppercasePointer { + | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/formatter/pass/individual_formatters.rs b/tests/ui/formatter/pass/individual_formatters.rs new file mode 100644 index 0000000..06dc5e4 --- /dev/null +++ b/tests/ui/formatter/pass/individual_formatters.rs @@ -0,0 +1,70 @@ +use masterror::Error; + +#[derive(Debug, Error)] +#[error("{value}")] +struct DisplayOnly { + value: &'static str, +} + +#[derive(Debug, Error)] +#[error("{value:?} {value:#?}")] +struct DebugPair { + value: &'static str, +} + +#[derive(Debug, Error)] +#[error("{value:x} {value:#x}")] +struct LowerHexPair { + value: u32, +} + +#[derive(Debug, Error)] +#[error("{value:X} {value:#X}")] +struct UpperHexPair { + value: u32, +} + +#[derive(Debug, Error)] +#[error("{value:b} {value:#b}")] +struct BinaryPair { + value: u16, +} + +#[derive(Debug, Error)] +#[error("{value:o} {value:#o}")] +struct OctalPair { + value: u16, +} + +#[derive(Debug, Error)] +#[error("{value:e} {value:#e}")] +struct LowerExpPair { + value: f64, +} + +#[derive(Debug, Error)] +#[error("{value:E} {value:#E}")] +struct UpperExpPair { + value: f64, +} + +#[derive(Debug, Error)] +#[error("{value:p} {value:#p}")] +struct PointerPair { + value: *const u32, +} + +fn main() { + let _ = DisplayOnly { value: "display" }.to_string(); + let _ = DebugPair { value: "debug" }.to_string(); + let _ = LowerHexPair { value: 0x5A5Au32 }.to_string(); + let _ = UpperHexPair { value: 0x5A5Au32 }.to_string(); + let _ = BinaryPair { value: 0b1010_1100u16 }.to_string(); + let _ = OctalPair { value: 0b1010_1100u16 }.to_string(); + let _ = LowerExpPair { value: 1234.5 }.to_string(); + let _ = UpperExpPair { value: 1234.5 }.to_string(); + let _ = PointerPair { + value: core::ptr::null::() + } + .to_string(); +}