use std::io;
use thiserror::Error;
use super::recovery::RecoveryPolicy;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum FramingError {
#[error("frame exceeds max length: {size} > {max}")]
OversizedFrame {
size: usize,
max: usize,
},
#[error("invalid frame length encoding")]
InvalidLengthEncoding,
#[error("incomplete frame header: have {have}, need {need}")]
IncompleteHeader {
have: usize,
need: usize,
},
#[error("frame checksum mismatch: expected {expected:#x}, got {actual:#x}")]
ChecksumMismatch {
expected: u32,
actual: u32,
},
#[error("empty frame not permitted")]
EmptyFrame,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum ProtocolError {
#[error("missing required header field: {field}")]
MissingHeader {
field: String,
},
#[error("unsupported protocol version: {version}")]
UnsupportedVersion {
version: u32,
},
#[error("unknown message type: {type_id}")]
UnknownMessageType {
type_id: u32,
},
#[error("sequence violation: expected {expected}, got {actual}")]
SequenceViolation {
expected: u64,
actual: u64,
},
#[error("invalid state transition: {from} -> {to}")]
InvalidStateTransition {
from: String,
to: String,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
pub enum EofError {
#[error("connection closed cleanly at frame boundary")]
CleanClose,
#[error("premature EOF: {bytes_received} bytes of {expected} byte frame received")]
MidFrame {
bytes_received: usize,
expected: usize,
},
#[error("premature EOF during header: {bytes_received} of {header_size} header bytes")]
MidHeader {
bytes_received: usize,
header_size: usize,
},
}
#[derive(Debug, Error)]
pub enum CodecError {
#[error("framing error: {0}")]
Framing(#[from] FramingError),
#[error("protocol error: {0}")]
Protocol(#[from] ProtocolError),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("EOF: {0}")]
Eof(#[from] EofError),
}
impl CodecError {
#[must_use]
pub fn default_recovery_policy(&self) -> RecoveryPolicy {
match self {
Self::Framing(FramingError::OversizedFrame { .. } | FramingError::EmptyFrame)
| Self::Protocol(_) => RecoveryPolicy::Drop,
Self::Framing(_) | Self::Io(_) | Self::Eof(_) => RecoveryPolicy::Disconnect,
}
}
#[must_use]
pub fn is_clean_close(&self) -> bool { matches!(self, Self::Eof(EofError::CleanClose)) }
#[must_use]
pub fn should_disconnect(&self) -> bool {
self.default_recovery_policy() == RecoveryPolicy::Disconnect
}
#[must_use]
pub fn error_type(&self) -> &'static str {
match self {
Self::Framing(_) => "framing",
Self::Protocol(_) => "protocol",
Self::Io(_) => "io",
Self::Eof(_) => "eof",
}
}
}
impl From<CodecError> for io::Error {
fn from(err: CodecError) -> Self {
match err {
CodecError::Io(e) => e,
CodecError::Framing(e) => {
io::Error::new(io::ErrorKind::InvalidData, CodecError::Framing(e))
}
CodecError::Protocol(e) => {
io::Error::new(io::ErrorKind::InvalidData, CodecError::Protocol(e))
}
CodecError::Eof(e) => io::Error::new(io::ErrorKind::UnexpectedEof, CodecError::Eof(e)),
}
}
}
#[cfg(test)]
#[path = "error_tests.rs"]
mod tests;