Skip to main content

nbt_rust/
error.rs

1use std::fmt;
2use std::io;
3
4use thiserror::Error as ThisError;
5
6pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct ErrorContext {
10    pub op: &'static str,
11    pub offset: usize,
12    pub field: Option<&'static str>,
13}
14
15impl fmt::Display for ErrorContext {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        if let Some(field) = self.field {
18            write!(
19                f,
20                "{} failed at offset={} field={}",
21                self.op, self.offset, field
22            )
23        } else {
24            write!(f, "{} failed at offset={}", self.op, self.offset)
25        }
26    }
27}
28
29#[derive(Debug, ThisError)]
30pub enum Error {
31    #[error("i/o error: {0}")]
32    Io(#[from] io::Error),
33    #[error("{context}: {source}")]
34    Context {
35        context: ErrorContext,
36        #[source]
37        source: Box<Error>,
38    },
39    #[error("serde error: {message}")]
40    Serde { message: String },
41    #[error("unknown tag id: {id}")]
42    UnknownTag { id: u8 },
43    #[error("invalid root tag type id: {id}")]
44    InvalidRoot { id: u8 },
45    #[error("invalid header ({detail}): expected={expected:?}, actual={actual:?}")]
46    InvalidHeader {
47        detail: &'static str,
48        expected: Option<u32>,
49        actual: Option<u32>,
50    },
51    #[error("maximum depth exceeded: depth={depth}, max_depth={max_depth}")]
52    DepthExceeded { depth: usize, max_depth: usize },
53    #[error("size exceeded for {field}: max={max}, actual={actual}")]
54    SizeExceeded {
55        field: &'static str,
56        max: usize,
57        actual: usize,
58    },
59    #[error("trailing payload bytes: unread={unread}")]
60    TrailingPayloadBytes { unread: usize },
61    #[error("unexpected TAG_End payload")]
62    UnexpectedEndTagPayload,
63    #[error("invalid UTF-8 for {field}")]
64    InvalidUtf8 { field: &'static str },
65    #[error("invalid list header: element_type_id={element_type_id}, length={length}")]
66    InvalidListHeader { element_type_id: u8, length: usize },
67    #[error("unexpected type ({context}): expected_id={expected_id}, actual_id={actual_id}")]
68    UnexpectedType {
69        context: &'static str,
70        expected_id: u8,
71        actual_id: u8,
72    },
73    #[error("invalid structure shape: {detail}")]
74    InvalidStructureShape { detail: &'static str },
75    #[error("invalid palette index: index={index}, palette_len={palette_len}")]
76    InvalidPaletteIndex { index: i32, palette_len: usize },
77    #[error("negative length for {field}: {value}")]
78    NegativeLength { field: &'static str, value: i32 },
79    #[error("length overflow for {field}: max={max}, actual={actual}")]
80    LengthOverflow {
81        field: &'static str,
82        max: usize,
83        actual: usize,
84    },
85    #[error("invalid varint: {detail}")]
86    InvalidVarint { detail: &'static str },
87}
88
89impl Error {
90    pub fn with_context(
91        self,
92        op: &'static str,
93        offset: usize,
94        field: Option<&'static str>,
95    ) -> Self {
96        Self::Context {
97            context: ErrorContext { op, offset, field },
98            source: Box::new(self),
99        }
100    }
101
102    pub fn innermost(&self) -> &Error {
103        match self {
104            Error::Context { source, .. } => source.innermost(),
105            _ => self,
106        }
107    }
108
109    pub fn has_context(&self, op: &'static str, field: Option<&'static str>) -> bool {
110        match self {
111            Error::Context { context, source } => {
112                if context.op == op && context.field == field {
113                    true
114                } else {
115                    source.has_context(op, field)
116                }
117            }
118            _ => false,
119        }
120    }
121}