facet_postcard/
error.rs

1//! Error types for postcard Tier-2 JIT parsing and serialization.
2
3extern crate alloc;
4
5use alloc::string::String;
6use alloc::vec::Vec;
7use core::fmt;
8
9/// Postcard parsing error with optional source context for diagnostics.
10#[derive(Debug, Clone)]
11pub struct PostcardError {
12    /// Error code from JIT
13    pub code: i32,
14    /// Position in input where error occurred
15    pub pos: usize,
16    /// Human-readable message
17    pub message: String,
18    /// Optional source bytes for diagnostics (hex dump context)
19    pub source_bytes: Option<Vec<u8>>,
20}
21
22impl fmt::Display for PostcardError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{} at position {}", self.message, self.pos)?;
25        if let Some(ref bytes) = self.source_bytes {
26            // Show hex dump context around error position
27            let context = self.hex_context(bytes);
28            if !context.is_empty() {
29                write!(f, "\n{}", context)?;
30            }
31        }
32        Ok(())
33    }
34}
35
36impl PostcardError {
37    /// Generate hex dump context around the error position.
38    fn hex_context(&self, bytes: &[u8]) -> String {
39        use alloc::format;
40
41        if bytes.is_empty() {
42            return String::new();
43        }
44
45        // Show up to 8 bytes before and after the error position
46        let start = self.pos.saturating_sub(8);
47        let end = (self.pos + 8).min(bytes.len());
48
49        let mut parts = Vec::new();
50        for (i, byte) in bytes[start..end].iter().enumerate() {
51            let abs_pos = start + i;
52            if abs_pos == self.pos {
53                parts.push(format!("[{:02x}]", byte));
54            } else {
55                parts.push(format!("{:02x}", byte));
56            }
57        }
58
59        format!(
60            "  bytes: {} (position {} marked with [])",
61            parts.join(" "),
62            self.pos
63        )
64    }
65
66    /// Attach source bytes for richer diagnostics.
67    pub fn with_source(mut self, bytes: &[u8]) -> Self {
68        self.source_bytes = Some(bytes.to_vec());
69        self
70    }
71}
72
73impl std::error::Error for PostcardError {}
74
75#[cfg(feature = "pretty-errors")]
76impl miette::Diagnostic for PostcardError {
77    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
78        let code_name = match self.code {
79            codes::UNEXPECTED_EOF => "postcard::unexpected_eof",
80            codes::INVALID_BOOL => "postcard::invalid_bool",
81            codes::VARINT_OVERFLOW => "postcard::varint_overflow",
82            codes::SEQ_UNDERFLOW => "postcard::seq_underflow",
83            codes::INVALID_UTF8 => "postcard::invalid_utf8",
84            codes::INVALID_OPTION_DISCRIMINANT => "postcard::invalid_option",
85            codes::INVALID_ENUM_DISCRIMINANT => "postcard::invalid_enum",
86            codes::UNSUPPORTED_OPAQUE_TYPE => "postcard::unsupported_opaque",
87            codes::UNEXPECTED_END_OF_INPUT => "postcard::eof",
88            codes::UNSUPPORTED => "postcard::unsupported",
89            _ => "postcard::unknown",
90        };
91        Some(Box::new(code_name))
92    }
93
94    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
95        let help = match self.code {
96            codes::UNEXPECTED_EOF | codes::UNEXPECTED_END_OF_INPUT => {
97                "The input data is truncated or incomplete"
98            }
99            codes::INVALID_BOOL => "Boolean values must be 0x00 (false) or 0x01 (true)",
100            codes::VARINT_OVERFLOW => "Varint encoding used too many continuation bytes",
101            codes::INVALID_UTF8 => "String data contains invalid UTF-8 bytes",
102            codes::INVALID_OPTION_DISCRIMINANT => {
103                "Option discriminant must be 0x00 (None) or 0x01 (Some)"
104            }
105            codes::INVALID_ENUM_DISCRIMINANT => "Enum variant index is out of range for this type",
106            _ => return None,
107        };
108        Some(Box::new(help))
109    }
110}
111
112/// Postcard JIT error codes.
113pub mod codes {
114    /// Unexpected end of input
115    pub const UNEXPECTED_EOF: i32 = -100;
116    /// Invalid boolean value (not 0 or 1)
117    pub const INVALID_BOOL: i32 = -101;
118    /// Varint overflow (too many continuation bytes)
119    pub const VARINT_OVERFLOW: i32 = -102;
120    /// Sequence underflow (decrement when remaining is 0)
121    pub const SEQ_UNDERFLOW: i32 = -103;
122    /// Invalid UTF-8 in string
123    pub const INVALID_UTF8: i32 = -104;
124    /// Invalid Option discriminant (not 0x00 or 0x01)
125    pub const INVALID_OPTION_DISCRIMINANT: i32 = -105;
126    /// Invalid enum variant discriminant (out of range)
127    pub const INVALID_ENUM_DISCRIMINANT: i32 = -106;
128    /// Unsupported opaque type (shouldn't happen if hint_opaque_scalar is correct)
129    pub const UNSUPPORTED_OPAQUE_TYPE: i32 = -107;
130    /// Unexpected end of input (for fixed-length reads)
131    pub const UNEXPECTED_END_OF_INPUT: i32 = -108;
132    /// Unsupported operation (triggers fallback)
133    pub const UNSUPPORTED: i32 = -1;
134}
135
136impl PostcardError {
137    /// Create an error from a JIT error code and position.
138    pub fn from_code(code: i32, pos: usize) -> Self {
139        let message = match code {
140            codes::UNEXPECTED_EOF => "unexpected end of input".to_string(),
141            codes::INVALID_BOOL => "invalid boolean value (expected 0 or 1)".to_string(),
142            codes::VARINT_OVERFLOW => "varint overflow".to_string(),
143            codes::SEQ_UNDERFLOW => "sequence underflow (internal error)".to_string(),
144            codes::INVALID_UTF8 => "invalid UTF-8 in string".to_string(),
145            codes::INVALID_OPTION_DISCRIMINANT => {
146                "invalid Option discriminant (expected 0x00 or 0x01)".to_string()
147            }
148            codes::INVALID_ENUM_DISCRIMINANT => "invalid enum variant discriminant".to_string(),
149            codes::UNSUPPORTED => "unsupported operation".to_string(),
150            _ => format!("unknown error code {}", code),
151        };
152        Self {
153            code,
154            pos,
155            message,
156            source_bytes: None,
157        }
158    }
159}
160
161/// Errors that can occur during postcard serialization.
162#[derive(Debug)]
163pub enum SerializeError {
164    /// The output buffer is too small to hold the serialized data
165    BufferTooSmall,
166    /// A custom error message
167    Custom(String),
168}
169
170impl fmt::Display for SerializeError {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            SerializeError::BufferTooSmall => write!(f, "Buffer too small for serialized data"),
174            SerializeError::Custom(msg) => write!(f, "{}", msg),
175        }
176    }
177}
178
179impl std::error::Error for SerializeError {}
180
181#[cfg(feature = "pretty-errors")]
182impl miette::Diagnostic for SerializeError {}