From 228cefee3671d4d87d1edfb188f1e231603fc27d Mon Sep 17 00:00:00 2001 From: leynos Date: Thu, 21 May 2026 01:22:50 +0200 Subject: [PATCH] Implement WireframeError error integration (#513) Implement `std::error::Error` for `WireframeError` so the default public error type can participate in standard error handling. Preserve transport and codec sources, and keep protocol variants source-free on stable Rust because the requested blanket-plus-unit specialization conflicts under coherence rules. Use thiserror's transparent `#[from]` conversion in `TestError` now that `WireframeError<()>` implements `Error`. --- src/error.rs | 5 ++--- src/testkit/result.rs | 8 ++------ tests/error_display.rs | 42 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/error.rs b/src/error.rs index 905508d3..4fecd38e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,14 +52,13 @@ impl std::fmt::Display for WireframeError { impl std::error::Error for WireframeError where - E: std::fmt::Debug + std::error::Error + 'static, + E: std::fmt::Debug + 'static, { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(error) => Some(error), - Self::Protocol(error) => Some(error), Self::Codec(error) => Some(error), - Self::DuplicateRoute(_) => None, + Self::DuplicateRoute(_) | Self::Protocol(_) => None, } } } diff --git a/src/testkit/result.rs b/src/testkit/result.rs index a6225b5d..676e1cac 100644 --- a/src/testkit/result.rs +++ b/src/testkit/result.rs @@ -21,8 +21,8 @@ pub enum TestError { #[error("{0}")] Msg(String), /// Root crate error surfaced while exercising a helper. - #[error("wireframe error: {0}")] - Wireframe(WireframeError), + #[error(transparent)] + Wireframe(#[from] WireframeError), /// Client-side error surfaced while exercising a helper. #[cfg(not(loom))] #[error(transparent)] @@ -95,10 +95,6 @@ impl From<&str> for TestError { fn from(value: &str) -> Self { Self::Msg(value.to_string()) } } -impl From for TestError { - fn from(value: WireframeError) -> Self { Self::Wireframe(value) } -} - impl From> for TestError { fn from(err: tokio::sync::mpsc::error::SendError) -> Self { Self::Msg(err.to_string()) } } diff --git a/tests/error_display.rs b/tests/error_display.rs index 0ead2d59..48cbf517 100644 --- a/tests/error_display.rs +++ b/tests/error_display.rs @@ -34,10 +34,10 @@ impl std::error::Error for ProtoErr {} fn wireframe_error_messages() { let proto = WireframeError::Protocol(ProtoErr); assert_eq!(proto.to_string(), "protocol error: ProtoErr"); - let proto_source = proto - .source() - .expect("protocol variant must expose its underlying source"); - assert_eq!(proto_source.to_string(), "boom"); + assert!( + proto.source().is_none(), + "Protocol variants should not expose a source without specialization" + ); let duplicate_route = WireframeError::::DuplicateRoute(7); assert_eq!( @@ -50,6 +50,40 @@ fn wireframe_error_messages() { ); } +#[test] +fn wireframe_error_unit_implements_error() { + let io = WireframeError::<()>::from_io(io::Error::other("socket closed")); + let io_source = io + .source() + .expect("io variant must expose its underlying source") + .downcast_ref::() + .expect("io source should be std::io::Error"); + assert_eq!(io_source.to_string(), "socket closed"); + + let codec = WireframeError::<()>::from_codec(CodecError::from(FramingError::EmptyFrame)); + let codec_source = codec + .source() + .expect("codec variant must expose its underlying source") + .downcast_ref::() + .expect("codec source should be wireframe::codec::CodecError"); + assert!( + matches!(codec_source, CodecError::Framing(FramingError::EmptyFrame)), + "codec source should preserve the original framing error" + ); + + let protocol = WireframeError::<()>::Protocol(()); + assert!( + protocol.source().is_none(), + "unit protocol errors should not expose an error source" + ); + + let duplicate_route = WireframeError::<()>::DuplicateRoute(7); + assert!( + duplicate_route.source().is_none(), + "DuplicateRoute should not expose an error source" + ); +} + #[test] fn wireframe_error_exposes_sources_for_io_and_codec() { let io = WireframeError::::from_io(io::Error::other("socket closed"));