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 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 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 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}