Skip to main content

basalt_types/
error.rs

1use std::string::FromUtf8Error;
2
3/// Crate-level result alias.
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Errors that can occur during encoding or decoding of Minecraft protocol types.
7#[derive(Debug, thiserror::Error)]
8pub enum Error {
9    #[error("buffer underflow: need {needed} bytes, got {available}")]
10    BufferUnderflow { needed: usize, available: usize },
11
12    #[error("invalid data: {0}")]
13    InvalidData(String),
14
15    #[error("varint too large")]
16    VarIntTooLarge,
17
18    #[error("string too long: {len} bytes, max {max}")]
19    StringTooLong { len: usize, max: usize },
20
21    #[error("invalid utf-8: {0}")]
22    InvalidUtf8(#[from] FromUtf8Error),
23
24    #[error("nbt error: {0}")]
25    Nbt(String),
26
27    /// An error with added context about where it occurred during
28    /// decoding (e.g., which field or packet was being decoded).
29    #[error("{context}: {source}")]
30    Context {
31        /// Human-readable description of what was being decoded.
32        context: String,
33        /// The underlying error.
34        source: Box<Error>,
35    },
36}
37
38impl Error {
39    /// Wraps this error with additional context about where it occurred.
40    ///
41    /// Use this to add field names, packet names, or other location
42    /// information to decode errors for easier debugging.
43    pub fn with_context(self, context: impl Into<String>) -> Self {
44        Self::Context {
45            context: context.into(),
46            source: Box::new(self),
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn display_buffer_underflow() {
57        let err = Error::BufferUnderflow {
58            needed: 4,
59            available: 2,
60        };
61        assert_eq!(err.to_string(), "buffer underflow: need 4 bytes, got 2");
62    }
63
64    #[test]
65    fn display_invalid_data() {
66        let err = Error::InvalidData("bad value".into());
67        assert_eq!(err.to_string(), "invalid data: bad value");
68    }
69
70    #[test]
71    fn display_varint_too_large() {
72        let err = Error::VarIntTooLarge;
73        assert_eq!(err.to_string(), "varint too large");
74    }
75
76    #[test]
77    fn display_string_too_long() {
78        let err = Error::StringTooLong {
79            len: 40000,
80            max: 32767,
81        };
82        assert_eq!(err.to_string(), "string too long: 40000 bytes, max 32767");
83    }
84
85    #[test]
86    fn display_nbt_error() {
87        let err = Error::Nbt("unexpected tag type".into());
88        assert_eq!(err.to_string(), "nbt error: unexpected tag type");
89    }
90
91    #[test]
92    fn context_wraps_error() {
93        let err = Error::BufferUnderflow {
94            needed: 8,
95            available: 3,
96        }
97        .with_context("decoding field 'x' of PlayerPosition");
98        assert_eq!(
99            err.to_string(),
100            "decoding field 'x' of PlayerPosition: buffer underflow: need 8 bytes, got 3"
101        );
102    }
103
104    #[test]
105    fn from_utf8_error() {
106        let invalid = vec![0xFF, 0xFE];
107        let utf8_err = String::from_utf8(invalid).unwrap_err();
108        let err: Error = utf8_err.into();
109        assert!(matches!(err, Error::InvalidUtf8(_)));
110    }
111}