use std::{error::Error, fmt, io};
type BoxedError = Box<dyn Error + Send + Sync + 'static>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CodecKind {
VarInt,
VarLong,
Boolean,
Byte,
UnsignedByte,
Short,
UnsignedShort,
Int,
Long,
String,
Identifier,
TextComponent,
JsonTextComponent,
}
impl fmt::Display for CodecKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::VarInt => formatter.write_str("VarInt"),
Self::VarLong => formatter.write_str("VarLong"),
Self::Boolean => formatter.write_str("Boolean"),
Self::Byte => formatter.write_str("Byte"),
Self::UnsignedByte => formatter.write_str("UnsignedByte"),
Self::Short => formatter.write_str("Short"),
Self::UnsignedShort => formatter.write_str("UnsignedShort"),
Self::Int => formatter.write_str("Int"),
Self::Long => formatter.write_str("Long"),
Self::String => formatter.write_str("String"),
Self::Identifier => formatter.write_str("Identifier"),
Self::TextComponent => formatter.write_str("TextComponent"),
Self::JsonTextComponent => formatter.write_str("JsonTextComponent"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CodecOperation {
Read,
Write,
}
impl fmt::Display for CodecOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read => formatter.write_str("reading"),
Self::Write => formatter.write_str("writing"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InvalidEncodingReason {
TooLong {
max_bytes: usize,
},
ValueOutOfRange {
terminal_byte: u8,
allowed_mask: u8,
},
InvalidBooleanValue {
value: u8,
},
StringTooLong {
max_bytes: usize,
},
TooManyUtf16CodeUnits {
max_code_units: usize,
},
NegativeLength {
value: i32,
},
InvalidUtf8 {
valid_up_to: usize,
error_len: Option<usize>,
},
InvalidIdentifier,
InvalidNbt,
InvalidJson,
InvalidTextComponentRootTag {
tag: u8,
},
}
impl fmt::Display for InvalidEncodingReason {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLong { max_bytes } => {
write!(formatter, "encoding exceeds the {max_bytes}-byte limit")
}
Self::ValueOutOfRange {
terminal_byte,
allowed_mask,
} => write!(
formatter,
"terminal byte 0x{terminal_byte:02X} contains bits outside mask 0x{allowed_mask:02X}"
),
Self::InvalidBooleanValue { value } => {
write!(formatter, "invalid boolean value 0x{value:02X}")
}
Self::StringTooLong { max_bytes } => {
write!(formatter, "string exceeds the {max_bytes}-byte UTF-8 limit")
}
Self::TooManyUtf16CodeUnits { max_code_units } => write!(
formatter,
"string exceeds the {max_code_units}-code-unit UTF-16 limit"
),
Self::NegativeLength { value } => {
write!(formatter, "length cannot be negative: {value}")
}
Self::InvalidUtf8 {
valid_up_to,
error_len: Some(error_len),
} => write!(
formatter,
"invalid UTF-8 sequence of {error_len} bytes at byte {valid_up_to}"
),
Self::InvalidUtf8 {
valid_up_to,
error_len: None,
} => write!(
formatter,
"incomplete UTF-8 sequence starting at byte {valid_up_to}"
),
Self::InvalidIdentifier => formatter.write_str("invalid Minecraft identifier"),
Self::InvalidNbt => formatter.write_str("invalid NBT data"),
Self::InvalidJson => formatter.write_str("invalid JSON data"),
Self::InvalidTextComponentRootTag { tag } => write!(
formatter,
"text component root tag must be TAG_String (8) or TAG_Compound (10), got {tag}"
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CodecErrorKind {
Io,
UnexpectedEof,
InvalidEncoding(InvalidEncodingReason),
}
#[derive(Debug)]
pub struct CodecError {
pub kind: CodecErrorKind,
codec: CodecKind,
contexts: Vec<CodecKind>,
operation: CodecOperation,
bytes_processed: usize,
source: Option<BoxedError>,
}
impl CodecError {
pub const fn kind(&self) -> CodecErrorKind {
self.kind
}
pub const fn codec(&self) -> CodecKind {
self.codec
}
pub fn context(&self) -> Option<CodecKind> {
self.contexts.last().copied()
}
pub fn contexts(&self) -> &[CodecKind] {
&self.contexts
}
pub const fn operation(&self) -> CodecOperation {
self.operation
}
pub const fn bytes_processed(&self) -> usize {
self.bytes_processed
}
pub fn io_error(&self) -> Option<&io::Error> {
self.source.as_deref()?.downcast_ref::<io::Error>()
}
pub fn with_context(mut self, context: CodecKind) -> Self {
self.contexts.push(context);
self
}
pub fn from_read_error(codec: CodecKind, bytes_processed: usize, source: io::Error) -> Self {
let kind = if source.kind() == io::ErrorKind::UnexpectedEof {
CodecErrorKind::UnexpectedEof
} else {
CodecErrorKind::Io
};
Self {
kind,
codec,
contexts: Vec::new(),
operation: CodecOperation::Read,
bytes_processed,
source: Some(Box::new(source)),
}
}
pub fn from_write_error(codec: CodecKind, bytes_processed: usize, source: io::Error) -> Self {
Self {
kind: CodecErrorKind::Io,
codec,
contexts: Vec::new(),
operation: CodecOperation::Write,
bytes_processed,
source: Some(Box::new(source)),
}
}
pub const fn invalid_encoding(
codec: CodecKind,
bytes_processed: usize,
reason: InvalidEncodingReason,
) -> Self {
Self::invalid_encoding_for_operation(codec, CodecOperation::Read, bytes_processed, reason)
}
pub const fn invalid_encoding_for_operation(
codec: CodecKind,
operation: CodecOperation,
bytes_processed: usize,
reason: InvalidEncodingReason,
) -> Self {
Self {
kind: CodecErrorKind::InvalidEncoding(reason),
codec,
contexts: Vec::new(),
operation,
bytes_processed,
source: None,
}
}
pub fn invalid_encoding_for_operation_with_source(
codec: CodecKind,
operation: CodecOperation,
bytes_processed: usize,
reason: InvalidEncodingReason,
source: impl Error + Send + Sync + 'static,
) -> Self {
Self {
kind: CodecErrorKind::InvalidEncoding(reason),
codec,
contexts: Vec::new(),
operation,
bytes_processed,
source: Some(Box::new(source)),
}
}
}
impl fmt::Display for CodecError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
CodecErrorKind::Io => write!(
formatter,
"I/O error while {} {} after {} bytes",
self.operation, self.codec, self.bytes_processed
)?,
CodecErrorKind::UnexpectedEof => write!(
formatter,
"unexpected end of input while reading {} after {} bytes",
self.codec, self.bytes_processed
)?,
CodecErrorKind::InvalidEncoding(reason) => write!(
formatter,
"invalid {} encoding after {} bytes: {reason}",
self.codec, self.bytes_processed
)?,
}
for context in &self.contexts {
write!(formatter, " while processing {context}")?;
}
if let Some(source) = &self.source {
write!(formatter, ": {source}")?;
}
Ok(())
}
}
impl Error for CodecError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source
.as_deref()
.map(|source| source as &(dyn Error + 'static))
}
}