Skip to main content

mcproto_codec/
error.rs

1use std::{error::Error, fmt, io};
2
3type BoxedError = Box<dyn Error + Send + Sync + 'static>;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6#[non_exhaustive]
7pub enum CodecKind {
8    VarInt,
9    VarLong,
10    Boolean,
11    Byte,
12    UnsignedByte,
13    Short,
14    UnsignedShort,
15    Int,
16    Long,
17    String,
18    Identifier,
19    TextComponent,
20    JsonTextComponent,
21}
22
23impl fmt::Display for CodecKind {
24    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::VarInt => formatter.write_str("VarInt"),
27            Self::VarLong => formatter.write_str("VarLong"),
28            Self::Boolean => formatter.write_str("Boolean"),
29            Self::Byte => formatter.write_str("Byte"),
30            Self::UnsignedByte => formatter.write_str("UnsignedByte"),
31            Self::Short => formatter.write_str("Short"),
32            Self::UnsignedShort => formatter.write_str("UnsignedShort"),
33            Self::Int => formatter.write_str("Int"),
34            Self::Long => formatter.write_str("Long"),
35            Self::String => formatter.write_str("String"),
36            Self::Identifier => formatter.write_str("Identifier"),
37            Self::TextComponent => formatter.write_str("TextComponent"),
38            Self::JsonTextComponent => formatter.write_str("JsonTextComponent"),
39        }
40    }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44#[non_exhaustive]
45pub enum CodecOperation {
46    Read,
47    Write,
48}
49
50impl fmt::Display for CodecOperation {
51    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::Read => formatter.write_str("reading"),
54            Self::Write => formatter.write_str("writing"),
55        }
56    }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60#[non_exhaustive]
61pub enum InvalidEncodingReason {
62    TooLong {
63        max_bytes: usize,
64    },
65    ValueOutOfRange {
66        terminal_byte: u8,
67        allowed_mask: u8,
68    },
69    InvalidBooleanValue {
70        value: u8,
71    },
72    StringTooLong {
73        max_bytes: usize,
74    },
75    TooManyUtf16CodeUnits {
76        max_code_units: usize,
77    },
78    NegativeLength {
79        value: i32,
80    },
81    InvalidUtf8 {
82        valid_up_to: usize,
83        error_len: Option<usize>,
84    },
85    InvalidNbt,
86    InvalidJson,
87    InvalidTextComponentRootTag {
88        tag: u8,
89    },
90}
91
92impl fmt::Display for InvalidEncodingReason {
93    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94        match self {
95            Self::TooLong { max_bytes } => {
96                write!(formatter, "encoding exceeds the {max_bytes}-byte limit")
97            }
98            Self::ValueOutOfRange {
99                terminal_byte,
100                allowed_mask,
101            } => write!(
102                formatter,
103                "terminal byte 0x{terminal_byte:02X} contains bits outside mask 0x{allowed_mask:02X}"
104            ),
105            Self::InvalidBooleanValue { value } => {
106                write!(formatter, "invalid boolean value 0x{value:02X}")
107            }
108            Self::StringTooLong { max_bytes } => {
109                write!(formatter, "string exceeds the {max_bytes}-byte UTF-8 limit")
110            }
111            Self::TooManyUtf16CodeUnits { max_code_units } => write!(
112                formatter,
113                "string exceeds the {max_code_units}-code-unit UTF-16 limit"
114            ),
115            Self::NegativeLength { value } => {
116                write!(formatter, "length cannot be negative: {value}")
117            }
118            Self::InvalidUtf8 {
119                valid_up_to,
120                error_len: Some(error_len),
121            } => write!(
122                formatter,
123                "invalid UTF-8 sequence of {error_len} bytes at byte {valid_up_to}"
124            ),
125            Self::InvalidUtf8 {
126                valid_up_to,
127                error_len: None,
128            } => write!(
129                formatter,
130                "incomplete UTF-8 sequence starting at byte {valid_up_to}"
131            ),
132            Self::InvalidNbt => formatter.write_str("invalid NBT data"),
133            Self::InvalidJson => formatter.write_str("invalid JSON data"),
134            Self::InvalidTextComponentRootTag { tag } => write!(
135                formatter,
136                "text component root tag must be TAG_String (8) or TAG_Compound (10), got {tag}"
137            ),
138        }
139    }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143#[non_exhaustive]
144pub enum CodecErrorKind {
145    Io,
146    UnexpectedEof,
147    InvalidEncoding(InvalidEncodingReason),
148}
149
150#[derive(Debug)]
151pub struct CodecError {
152    pub kind: CodecErrorKind,
153    codec: CodecKind,
154    contexts: Vec<CodecKind>,
155    operation: CodecOperation,
156    bytes_processed: usize,
157    source: Option<BoxedError>,
158}
159
160impl CodecError {
161    pub const fn kind(&self) -> CodecErrorKind {
162        self.kind
163    }
164
165    pub const fn codec(&self) -> CodecKind {
166        self.codec
167    }
168
169    pub fn context(&self) -> Option<CodecKind> {
170        self.contexts.last().copied()
171    }
172
173    pub fn contexts(&self) -> &[CodecKind] {
174        &self.contexts
175    }
176
177    pub const fn operation(&self) -> CodecOperation {
178        self.operation
179    }
180
181    /// Returns the number of bytes successfully processed before the error.
182    pub const fn bytes_processed(&self) -> usize {
183        self.bytes_processed
184    }
185
186    pub fn io_error(&self) -> Option<&io::Error> {
187        self.source.as_deref()?.downcast_ref::<io::Error>()
188    }
189
190    /// Adds the outer codec that was active when this error occurred.
191    pub fn with_context(mut self, context: CodecKind) -> Self {
192        self.contexts.push(context);
193        self
194    }
195
196    pub fn from_read_error(codec: CodecKind, bytes_processed: usize, source: io::Error) -> Self {
197        let kind = if source.kind() == io::ErrorKind::UnexpectedEof {
198            CodecErrorKind::UnexpectedEof
199        } else {
200            CodecErrorKind::Io
201        };
202
203        Self {
204            kind,
205            codec,
206            contexts: Vec::new(),
207            operation: CodecOperation::Read,
208            bytes_processed,
209            source: Some(Box::new(source)),
210        }
211    }
212
213    pub fn from_write_error(codec: CodecKind, bytes_processed: usize, source: io::Error) -> Self {
214        Self {
215            kind: CodecErrorKind::Io,
216            codec,
217            contexts: Vec::new(),
218            operation: CodecOperation::Write,
219            bytes_processed,
220            source: Some(Box::new(source)),
221        }
222    }
223    /// Tips:encoding是编码格式,不是encode过程
224    pub const fn invalid_encoding(
225        codec: CodecKind,
226        bytes_processed: usize,
227        reason: InvalidEncodingReason,
228    ) -> Self {
229        Self::invalid_encoding_for_operation(codec, CodecOperation::Read, bytes_processed, reason)
230    }
231
232    pub const fn invalid_encoding_for_operation(
233        codec: CodecKind,
234        operation: CodecOperation,
235        bytes_processed: usize,
236        reason: InvalidEncodingReason,
237    ) -> Self {
238        Self {
239            kind: CodecErrorKind::InvalidEncoding(reason),
240            codec,
241            contexts: Vec::new(),
242            operation,
243            bytes_processed,
244            source: None,
245        }
246    }
247
248    pub fn invalid_encoding_for_operation_with_source(
249        codec: CodecKind,
250        operation: CodecOperation,
251        bytes_processed: usize,
252        reason: InvalidEncodingReason,
253        source: impl Error + Send + Sync + 'static,
254    ) -> Self {
255        Self {
256            kind: CodecErrorKind::InvalidEncoding(reason),
257            codec,
258            contexts: Vec::new(),
259            operation,
260            bytes_processed,
261            source: Some(Box::new(source)),
262        }
263    }
264}
265
266impl fmt::Display for CodecError {
267    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self.kind {
269            CodecErrorKind::Io => write!(
270                formatter,
271                "I/O error while {} {} after {} bytes",
272                self.operation, self.codec, self.bytes_processed
273            )?,
274            CodecErrorKind::UnexpectedEof => write!(
275                formatter,
276                "unexpected end of input while reading {} after {} bytes",
277                self.codec, self.bytes_processed
278            )?,
279            CodecErrorKind::InvalidEncoding(reason) => write!(
280                formatter,
281                "invalid {} encoding after {} bytes: {reason}",
282                self.codec, self.bytes_processed
283            )?,
284        }
285
286        for context in &self.contexts {
287            write!(formatter, " while processing {context}")?;
288        }
289
290        if let Some(source) = &self.source {
291            write!(formatter, ": {source}")?;
292        }
293
294        Ok(())
295    }
296}
297
298impl Error for CodecError {
299    fn source(&self) -> Option<&(dyn Error + 'static)> {
300        self.source
301            .as_deref()
302            .map(|source| source as &(dyn Error + 'static))
303    }
304}