use std::{borrow::Cow, error::Error as StdError, fmt};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DecodeErrorKind {
Truncated,
TrailingBytes,
InvalidUtf8,
InvalidTag,
InvalidBool,
NonCanonicalVarint,
VarintOverflow,
LengthExceedsInput,
DuplicateMapKey,
UnsupportedVersion,
MessageTooLarge,
Conversion,
}
impl DecodeErrorKind {
fn describe(self) -> &'static str {
match self {
DecodeErrorKind::Truncated => "input ended inside a value",
DecodeErrorKind::TrailingBytes => "trailing bytes after the message",
DecodeErrorKind::InvalidUtf8 => "invalid UTF-8 in str",
DecodeErrorKind::InvalidTag => "unknown enum value, union tag or optional tag",
DecodeErrorKind::InvalidBool => "bool byte is neither 0 nor 1",
DecodeErrorKind::NonCanonicalVarint => "varint is not minimally encoded",
DecodeErrorKind::VarintOverflow => "varint does not fit in 64 bits",
DecodeErrorKind::LengthExceedsInput => "length exceeds the remaining input",
DecodeErrorKind::DuplicateMapKey => "duplicate map key",
DecodeErrorKind::UnsupportedVersion => "unsupported version",
DecodeErrorKind::MessageTooLarge => "message is larger than 4 GiB",
DecodeErrorKind::Conversion => "version conversion failed",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Segment {
Field(&'static str),
Index(u64),
}
#[derive(Debug)]
struct Inner {
kind: DecodeErrorKind,
offset: usize,
path: Vec<Segment>,
detail: Option<Cow<'static, str>>,
source: Option<anyhow::Error>,
}
pub struct DecodeError(Box<Inner>);
impl DecodeError {
#[cold]
pub fn new(kind: DecodeErrorKind, offset: usize) -> Self {
DecodeError(Box::new(Inner {
kind,
offset,
path: Vec::new(),
detail: None,
source: None,
}))
}
#[cold]
pub fn with_detail(
kind: DecodeErrorKind,
offset: usize,
detail: impl Into<Cow<'static, str>>,
) -> Self {
let mut error = DecodeError::new(kind, offset);
error.0.detail = Some(detail.into());
error
}
#[cold]
pub fn conversion(source: anyhow::Error) -> Self {
let mut error = DecodeError::new(DecodeErrorKind::Conversion, 0);
error.0.source = Some(source);
error
}
pub fn into_conversion(self) -> Result<anyhow::Error, Self> {
let mut inner = self.0;
match inner.source.take() {
Some(source) => Ok(source),
None => Err(DecodeError(inner)),
}
}
pub fn kind(&self) -> DecodeErrorKind {
self.0.kind
}
pub fn offset(&self) -> usize {
self.0.offset
}
pub fn path(&self) -> String {
let mut path = String::new();
for segment in self.0.path.iter().rev() {
match segment {
Segment::Field(name) => {
if !path.is_empty() {
path.push('.');
}
path.push_str(name);
}
Segment::Index(index) => {
path.push('[');
path.push_str(&index.to_string());
path.push(']');
}
}
}
path
}
#[cold]
pub fn in_field(mut self, name: &'static str) -> Self {
self.0.path.push(Segment::Field(name));
self
}
#[cold]
pub fn in_index(mut self, index: u64) -> Self {
self.0.path.push(Segment::Index(index));
self
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.kind.describe())?;
if let Some(detail) = &self.0.detail {
write!(f, " ({detail})")?;
}
let path = self.path();
if !path.is_empty() {
write!(f, " at {path}")?;
}
write!(f, " (byte {})", self.0.offset)?;
if let Some(source) = &self.0.source {
write!(f, ": {source}")?;
}
Ok(())
}
}
impl fmt::Debug for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DecodeError")
.field("kind", &self.0.kind)
.field("offset", &self.0.offset)
.field("path", &self.path())
.field("detail", &self.0.detail)
.field("source", &self.0.source)
.finish()
}
}
impl StdError for DecodeError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0
.source
.as_ref()
.map(|source| source.as_ref() as &(dyn StdError + 'static))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EncodeError {
BufferTooSmall { needed: usize, available: usize },
UnsupportedVersion(u16),
Conversion(anyhow::Error),
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::BufferTooSmall { needed, available } => write!(
f,
"output buffer holds {available} bytes but the value needs {needed}"
),
EncodeError::UnsupportedVersion(version) => {
write!(f, "unsupported version {version}")
}
EncodeError::Conversion(error) => write!(f, "version conversion failed: {error}"),
}
}
}
impl StdError for EncodeError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
EncodeError::BufferTooSmall { .. } | EncodeError::UnsupportedVersion(_) => None,
EncodeError::Conversion(error) => Some(error.as_ref()),
}
}
}