use std::fmt;
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeError {
BadMagic,
VersionMismatch {
supported: u8,
received: u8,
},
UnknownMessageType(u8),
UnknownDurability(u8),
HeaderCrcMismatch {
expected: u32,
computed: u32,
},
PayloadCrcMismatch {
expected: u32,
computed: u32,
},
TruncatedFrame,
PayloadTooLarge {
len: usize,
cap: usize,
},
ReservedFlagsSet {
flags: u8,
},
TrailingBytes {
extra: usize,
},
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadMagic => {
write!(f, "bad magic bytes: not a weir frame")
}
Self::VersionMismatch {
supported,
received,
} => {
write!(
f,
"wire version mismatch: daemon supports v{supported}, frame carries v{received}"
)
}
Self::UnknownMessageType(t) => {
write!(f, "unknown message type: {t:#04x}")
}
Self::UnknownDurability(d) => {
write!(f, "unknown durability byte: {d:#04x}")
}
Self::HeaderCrcMismatch { expected, computed } => {
write!(
f,
"header CRC mismatch: expected {expected:#010x}, computed {computed:#010x}"
)
}
Self::PayloadCrcMismatch { expected, computed } => {
write!(
f,
"payload CRC mismatch: expected {expected:#010x}, computed {computed:#010x}"
)
}
Self::TruncatedFrame => {
write!(f, "truncated frame: buffer shorter than declared length")
}
Self::PayloadTooLarge { len, cap } => {
write!(f, "payload length {len} exceeds cap {cap}")
}
Self::ReservedFlagsSet { flags } => {
write!(f, "reserved flags byte must be zero, got {flags:#04x}")
}
Self::TrailingBytes { extra } => {
write!(
f,
"buffer has {extra} byte(s) past the declared frame length"
)
}
}
}
}
impl std::error::Error for DecodeError {}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum WeirError {
Decode(DecodeError),
}
impl fmt::Display for WeirError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Decode(e) => write!(f, "decode error: {e}"),
}
}
}
impl std::error::Error for WeirError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Decode(e) => Some(e),
}
}
}
impl From<DecodeError> for WeirError {
fn from(e: DecodeError) -> Self {
Self::Decode(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_error_display_is_human_readable() {
let e = DecodeError::VersionMismatch {
supported: 1,
received: 2,
};
let s = e.to_string();
assert!(
s.contains("v1"),
"display should contain supported version: {s}"
);
assert!(
s.contains("v2"),
"display should contain received version: {s}"
);
}
#[test]
fn every_decode_error_variant_displays_its_datum() {
let cases: Vec<(DecodeError, &str)> = vec![
(DecodeError::BadMagic, "magic"),
(
DecodeError::VersionMismatch {
supported: 1,
received: 2,
},
"v2",
),
(DecodeError::UnknownMessageType(0xff), "0xff"),
(DecodeError::UnknownDurability(0xaa), "0xaa"),
(
DecodeError::HeaderCrcMismatch {
expected: 1,
computed: 2,
},
"CRC",
),
(
DecodeError::PayloadCrcMismatch {
expected: 1,
computed: 2,
},
"CRC",
),
(DecodeError::TruncatedFrame, "truncated"),
(DecodeError::PayloadTooLarge { len: 99, cap: 10 }, "99"),
(DecodeError::ReservedFlagsSet { flags: 0x01 }, "0x01"),
(DecodeError::TrailingBytes { extra: 7 }, "7"),
];
for (e, needle) in cases {
let s = e.to_string();
assert!(!s.is_empty(), "{e:?}: Display must be non-empty");
assert!(
s.contains(needle),
"{e:?}: Display {s:?} must contain {needle:?}"
);
}
}
#[test]
fn unknown_byte_error_structs_render_the_raw_byte() {
assert!(crate::UnknownMessageType(0xff).to_string().contains("0xff"));
assert!(crate::UnknownDurability(0xaa).to_string().contains("0xaa"));
assert!(crate::UnknownNackReason(0x0a).to_string().contains("0x0a"));
}
#[test]
fn weir_error_source_chains_to_decode_error() {
use std::error::Error;
let e = WeirError::from(DecodeError::BadMagic);
assert!(e.source().is_some());
}
}