use std::io;
use crate::WireframeError;
#[derive(Debug, thiserror::Error)]
pub enum ClientProtocolError {
#[error("failed to deserialize message")]
Deserialize(#[source] Box<dyn std::error::Error + Send + Sync>),
}
pub type ClientWireframeError = WireframeError<ClientProtocolError>;
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error(transparent)]
Wireframe(#[from] ClientWireframeError),
#[error("failed to serialize message")]
Serialize(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("failed to encode preamble")]
PreambleEncode(#[source] bincode::error::EncodeError),
#[error("failed to write preamble: {0}")]
PreambleWrite(#[source] io::Error),
#[error("failed to read preamble response: {0}")]
PreambleRead(#[source] io::Error),
#[error("preamble exchange timed out")]
PreambleTimeout,
#[error("correlation ID mismatch: expected {expected:?}, received {received:?}")]
CorrelationMismatch {
expected: Option<u64>,
received: Option<u64>,
},
#[rustfmt::skip]
#[error("correlation ID mismatch in streaming response: expected {expected:?}, received {received:?}")]
StreamCorrelationMismatch {
expected: Option<u64>,
received: Option<u64>,
},
}
impl ClientError {
pub(crate) fn decode(source: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self::Wireframe(ClientWireframeError::Protocol(
ClientProtocolError::Deserialize(source),
))
}
pub(crate) fn disconnected() -> Self {
Self::from(io::Error::new(
io::ErrorKind::UnexpectedEof,
"connection closed by peer",
))
}
#[must_use]
pub fn should_recycle_connection(&self) -> bool {
match self {
Self::Wireframe(_)
| Self::PreambleWrite(_)
| Self::PreambleRead(_)
| Self::PreambleTimeout
| Self::CorrelationMismatch { .. }
| Self::StreamCorrelationMismatch { .. } => true,
Self::Serialize(_) | Self::PreambleEncode(_) => false,
}
}
}
impl From<io::Error> for ClientError {
fn from(value: io::Error) -> Self { Self::Wireframe(ClientWireframeError::from_io(value)) }
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(io::ErrorKind::BrokenPipe)]
#[case(io::ErrorKind::UnexpectedEof)]
fn io_errors_map_to_wireframe_transport_variant(#[case] kind: io::ErrorKind) {
let err = ClientError::from(io::Error::new(kind, "transport failure"));
assert!(
matches!(err, ClientError::Wireframe(ClientWireframeError::Io(_))),
"I/O errors should map to ClientError::Wireframe(WireframeError::Io(_))"
);
}
#[test]
fn decode_helper_maps_to_wireframe_protocol_variant() {
let err = ClientError::decode(Box::new(io::Error::new(
io::ErrorKind::InvalidData,
"decode failure",
)));
assert!(
matches!(
err,
ClientError::Wireframe(ClientWireframeError::Protocol(
ClientProtocolError::Deserialize(_)
))
),
"decode errors should map to ClientError::Wireframe(WireframeError::Protocol(_))"
);
}
#[test]
fn should_recycle_connection_returns_true_for_transport_and_protocol_failures() {
let io_error = ClientError::from(io::Error::new(
io::ErrorKind::BrokenPipe,
"transport failure",
));
let decode_error = ClientError::decode(Box::new(io::Error::new(
io::ErrorKind::InvalidData,
"decode failure",
)));
let correlation_error = ClientError::CorrelationMismatch {
expected: Some(1),
received: Some(2),
};
assert!(io_error.should_recycle_connection());
assert!(decode_error.should_recycle_connection());
assert!(correlation_error.should_recycle_connection());
}
#[test]
fn should_recycle_connection_returns_false_for_serialize_failures() {
let error = ClientError::Serialize(Box::new(io::Error::other("serialize failure")));
assert!(!error.should_recycle_connection());
}
}