use core::fmt;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SerialError {
UnexpectedEof {
needed: usize,
remaining: usize,
},
InvalidLength {
declared: u64,
remaining: usize,
},
VarintOverflow,
IntegerOutOfRange,
InvalidBool {
byte: u8,
},
InvalidUtf8,
InvalidTag {
kind: &'static str,
tag: u8,
},
TrailingBytes {
remaining: usize,
},
#[cfg(feature = "std")]
Io {
kind: std::io::ErrorKind,
message: alloc::string::String,
},
}
impl fmt::Display for SerialError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEof { needed, remaining } => write!(
f,
"unexpected end of input: needed {needed} more byte(s), {remaining} remaining"
),
Self::InvalidLength {
declared,
remaining,
} => write!(
f,
"length prefix exceeds remaining buffer: declared {declared}, remaining {remaining}"
),
Self::VarintOverflow => {
f.write_str("varint exceeds the maximum byte count for its target width")
}
Self::IntegerOutOfRange => {
f.write_str("decoded integer does not fit in the requested width")
}
Self::InvalidBool { byte } => write!(f, "invalid boolean byte: 0x{byte:02x}"),
Self::InvalidUtf8 => f.write_str("length-prefixed bytes were not valid UTF-8"),
Self::InvalidTag { kind, tag } => write!(f, "invalid {kind} tag: 0x{tag:02x}"),
Self::TrailingBytes { remaining } => {
write!(
f,
"trailing input after strict decode: {remaining} byte(s) unread"
)
}
#[cfg(feature = "std")]
Self::Io { kind, message } => write!(f, "I/O error ({kind:?}): {message}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SerialError {}
pub type Result<T> = core::result::Result<T, SerialError>;
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use alloc::string::ToString;
#[test]
fn display_unexpected_eof_reports_counts() {
let err = SerialError::UnexpectedEof {
needed: 4,
remaining: 1,
};
let msg = err.to_string();
assert!(msg.contains("needed 4"));
assert!(msg.contains("1 remaining"));
}
#[test]
fn display_invalid_length_reports_declared_and_remaining() {
let err = SerialError::InvalidLength {
declared: 1 << 20,
remaining: 16,
};
let msg = err.to_string();
assert!(msg.contains("1048576"));
assert!(msg.contains("16"));
}
#[test]
fn display_invalid_bool_is_hex_with_zero_pad() {
let err = SerialError::InvalidBool { byte: 0x2a };
assert_eq!(err.to_string(), "invalid boolean byte: 0x2a");
}
#[test]
fn display_invalid_tag_carries_kind_and_byte() {
let err = SerialError::InvalidTag {
kind: "Option",
tag: 0x7f,
};
assert!(err.to_string().contains("Option"));
assert!(err.to_string().contains("0x7f"));
}
#[test]
fn equality_distinguishes_variants() {
let a = SerialError::VarintOverflow;
let b = SerialError::VarintOverflow;
let c = SerialError::IntegerOutOfRange;
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn clone_preserves_variant() {
let err = SerialError::TrailingBytes { remaining: 8 };
let cloned = err.clone();
assert_eq!(err, cloned);
}
#[test]
fn debug_format_does_not_panic() {
let _ = format!("{:?}", SerialError::InvalidUtf8);
}
}