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}