Skip to main content

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/// Postcard JIT error codes.
76pub mod codes {
77    /// Unexpected end of input
78    pub const UNEXPECTED_EOF: i32 = -100;
79    /// Invalid boolean value (not 0 or 1)
80    pub const INVALID_BOOL: i32 = -101;
81    /// Varint overflow (too many continuation bytes)
82    pub const VARINT_OVERFLOW: i32 = -102;
83    /// Sequence underflow (decrement when remaining is 0)
84    pub const SEQ_UNDERFLOW: i32 = -103;
85    /// Invalid UTF-8 in string
86    pub const INVALID_UTF8: i32 = -104;
87    /// Invalid Option discriminant (not 0x00 or 0x01)
88    pub const INVALID_OPTION_DISCRIMINANT: i32 = -105;
89    /// Invalid enum variant discriminant (out of range)
90    pub const INVALID_ENUM_DISCRIMINANT: i32 = -106;
91    /// Unsupported opaque type (shouldn't happen if hint_opaque_scalar is correct)
92    pub const UNSUPPORTED_OPAQUE_TYPE: i32 = -107;
93    /// Unexpected end of input (for fixed-length reads)
94    pub const UNEXPECTED_END_OF_INPUT: i32 = -108;
95    /// Unsupported operation (triggers fallback)
96    pub const UNSUPPORTED: i32 = -1;
97}
98
99impl PostcardError {
100    /// Create an error from a JIT error code and position.
101    pub fn from_code(code: i32, pos: usize) -> Self {
102        let message = match code {
103            codes::UNEXPECTED_EOF => "unexpected end of input".to_string(),
104            codes::INVALID_BOOL => "invalid boolean value (expected 0 or 1)".to_string(),
105            codes::VARINT_OVERFLOW => "varint overflow".to_string(),
106            codes::SEQ_UNDERFLOW => "sequence underflow (internal error)".to_string(),
107            codes::INVALID_UTF8 => "invalid UTF-8 in string".to_string(),
108            codes::INVALID_OPTION_DISCRIMINANT => {
109                "invalid Option discriminant (expected 0x00 or 0x01)".to_string()
110            }
111            codes::INVALID_ENUM_DISCRIMINANT => "invalid enum variant discriminant".to_string(),
112            codes::UNSUPPORTED => "unsupported operation".to_string(),
113            _ => format!("unknown error code {}", code),
114        };
115        Self {
116            code,
117            pos,
118            message,
119            source_bytes: None,
120        }
121    }
122}
123
124/// Errors that can occur during postcard serialization.
125#[derive(Debug)]
126pub enum SerializeError {
127    /// The output buffer is too small to hold the serialized data
128    BufferTooSmall,
129    /// A custom error message
130    Custom(String),
131}
132
133impl fmt::Display for SerializeError {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            SerializeError::BufferTooSmall => write!(f, "Buffer too small for serialized data"),
137            SerializeError::Custom(msg) => write!(f, "{}", msg),
138        }
139    }
140}
141
142impl std::error::Error for SerializeError {}