Skip to main content

multi_cbor/
error.rs

1//! When serializing or deserializing CBOR goes wrong.
2use core::fmt;
3use core::result;
4use serde::de;
5use serde::ser;
6#[cfg(feature = "std")]
7use std::error;
8#[cfg(feature = "std")]
9use std::io;
10
11/// This type represents all possible errors that can occur when serializing or deserializing CBOR
12/// data.
13///
14/// # Backtrace Support
15///
16/// When the `std` feature is enabled and running on Rust 1.65+, errors automatically capture
17/// a backtrace when created. Access it via the `std::error::Error::backtrace()` method.
18pub struct Error(ErrorImpl);
19
20/// Alias for a `Result` with the error type `multi_cbor::Error`.
21pub type Result<T> = result::Result<T, Error>;
22
23/// Categorizes the cause of a `multi_cbor::Error`.
24#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25pub enum Category {
26    /// The error was caused by a failure to read or write bytes on an IO stream.
27    Io,
28    /// The error was caused by input that was not syntactically valid CBOR.
29    Syntax,
30    /// The error was caused by input data that was semantically incorrect.
31    Data,
32    /// The error was caused by prematurely reaching the end of the input data.
33    Eof,
34}
35
36impl Error {
37    /// The byte offset at which the error occurred.
38    #[must_use]
39    pub const fn offset(&self) -> u64 {
40        self.0.offset
41    }
42
43    pub(crate) fn syntax(code: ErrorCode, offset: u64) -> Self {
44        Self(ErrorImpl {
45            code,
46            offset,
47            #[cfg(feature = "std")]
48            _backtrace: std::backtrace::Backtrace::capture(),
49        })
50    }
51
52    #[cfg(feature = "std")]
53    pub(crate) fn io(error: io::Error) -> Self {
54        Self(ErrorImpl {
55            code: ErrorCode::Io(error),
56            offset: 0,
57            _backtrace: std::backtrace::Backtrace::capture(),
58        })
59    }
60
61    #[cfg(all(not(feature = "std"), feature = "unsealed_read_write"))]
62    /// Creates an error signalling that the underlying `Read` encountered an I/O error.
63    #[must_use]
64    pub fn io() -> Self {
65        Self(ErrorImpl {
66            code: ErrorCode::Io,
67            offset: 0,
68        })
69    }
70
71    #[cfg(feature = "unsealed_read_write")]
72    /// Creates an error signalling that the scratch buffer was too small to fit the data.
73    #[must_use]
74    pub fn scratch_too_small(offset: u64) -> Self {
75        Self(ErrorImpl {
76            code: ErrorCode::ScratchTooSmall,
77            offset,
78            #[cfg(feature = "std")]
79            _backtrace: std::backtrace::Backtrace::capture(),
80        })
81    }
82
83    #[cfg(not(feature = "unsealed_read_write"))]
84    pub(crate) fn scratch_too_small(offset: u64) -> Self {
85        Self(ErrorImpl {
86            code: ErrorCode::ScratchTooSmall,
87            offset,
88            #[cfg(feature = "std")]
89            _backtrace: std::backtrace::Backtrace::capture(),
90        })
91    }
92
93    #[cfg(feature = "unsealed_read_write")]
94    /// Creates an error with a custom message.
95    ///
96    /// **Note**: When the "std" feature is disabled, the message will be discarded.
97    pub fn message<T: fmt::Display>(msg: T) -> Self {
98        #[cfg(not(feature = "std"))]
99        {
100            let _ = msg;
101            Self(ErrorImpl {
102                code: ErrorCode::Message,
103                offset: 0,
104            })
105        }
106        #[cfg(feature = "std")]
107        {
108            Self(ErrorImpl {
109                code: ErrorCode::Message(msg.to_string()),
110                offset: 0,
111                _backtrace: std::backtrace::Backtrace::capture(),
112            })
113        }
114    }
115
116    #[cfg(not(feature = "unsealed_read_write"))]
117    pub(crate) fn message<T: fmt::Display>(msg: T) -> Self {
118        #[cfg(not(feature = "std"))]
119        {
120            let _ = msg;
121            Self(ErrorImpl {
122                code: ErrorCode::Message,
123                offset: 0,
124            })
125        }
126        #[cfg(feature = "std")]
127        {
128            Self(ErrorImpl {
129                code: ErrorCode::Message(msg.to_string()),
130                offset: 0,
131                _backtrace: std::backtrace::Backtrace::capture(),
132            })
133        }
134    }
135
136    #[cfg(feature = "unsealed_read_write")]
137    /// Creates an error signalling that the underlying read
138    /// encountered an end of input.
139    #[must_use]
140    pub fn eof(offset: u64) -> Self {
141        Self(ErrorImpl {
142            code: ErrorCode::EofWhileParsingValue,
143            offset,
144            #[cfg(feature = "std")]
145            _backtrace: std::backtrace::Backtrace::capture(),
146        })
147    }
148
149    /// Categorizes the cause of this error.
150    #[must_use]
151    pub const fn classify(&self) -> Category {
152        match self.0.code {
153            #[cfg(feature = "std")]
154            ErrorCode::Message(_) => Category::Data,
155            #[cfg(not(feature = "std"))]
156            ErrorCode::Message => Category::Data,
157            #[cfg(feature = "std")]
158            ErrorCode::Io(_) => Category::Io,
159            #[cfg(not(feature = "std"))]
160            ErrorCode::Io => Category::Io,
161            ErrorCode::ScratchTooSmall => Category::Io,
162            ErrorCode::EofWhileParsingValue
163            | ErrorCode::EofWhileParsingArray
164            | ErrorCode::EofWhileParsingMap => Category::Eof,
165            ErrorCode::LengthOutOfRange
166            | ErrorCode::InvalidUtf8
167            | ErrorCode::UnassignedCode
168            | ErrorCode::UnexpectedCode
169            | ErrorCode::TrailingData
170            | ErrorCode::ArrayTooShort
171            | ErrorCode::ArrayTooLong
172            | ErrorCode::RecursionLimitExceeded
173            | ErrorCode::WrongEnumFormat
174            | ErrorCode::WrongStructFormat
175            | ErrorCode::ArraySizeLimitExceeded
176            | ErrorCode::MapSizeLimitExceeded
177            | ErrorCode::IndefiniteIterationLimitExceeded => Category::Syntax,
178        }
179    }
180
181    /// Returns true if this error was caused by a failure to read or write bytes on an IO stream.
182    #[must_use]
183    pub const fn is_io(&self) -> bool {
184        matches!(self.classify(), Category::Io)
185    }
186
187    /// Returns true if this error was caused by input that was not syntactically valid CBOR.
188    #[must_use]
189    pub const fn is_syntax(&self) -> bool {
190        matches!(self.classify(), Category::Syntax)
191    }
192
193    /// Returns true if this error was caused by data that was semantically incorrect.
194    #[must_use]
195    pub const fn is_data(&self) -> bool {
196        matches!(self.classify(), Category::Data)
197    }
198
199    /// Returns true if this error was caused by prematurely reaching the end of the input data.
200    #[must_use]
201    pub const fn is_eof(&self) -> bool {
202        matches!(self.classify(), Category::Eof)
203    }
204
205    /// Returns true if this error was caused by the scratch buffer being too small.
206    ///
207    /// Note this being `true` implies that `is_io()` is also `true`.
208    #[must_use]
209    pub const fn is_scratch_too_small(&self) -> bool {
210        matches!(self.0.code, ErrorCode::ScratchTooSmall)
211    }
212}
213
214#[cfg(feature = "std")]
215impl error::Error for Error {
216    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
217        match self.0.code {
218            ErrorCode::Io(ref err) => Some(err),
219            _ => None,
220        }
221    }
222
223    // Note: Once std::error::Error::provide() is stabilized, we can expose the backtrace
224    // For now, the backtrace is captured and stored but not exposed via the Error trait
225}
226
227impl fmt::Display for Error {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        if self.0.offset == 0 {
230            fmt::Display::fmt(&self.0.code, f)
231        } else {
232            write!(f, "{} at offset {}", self.0.code, self.0.offset)
233        }
234    }
235}
236
237impl fmt::Debug for Error {
238    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
239        fmt::Debug::fmt(&self.0, fmt)
240    }
241}
242
243impl de::Error for Error {
244    fn custom<T: fmt::Display>(msg: T) -> Self {
245        Self::message(msg)
246    }
247
248    fn invalid_type(unexp: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
249        if unexp == de::Unexpected::Unit {
250            Self::custom(format_args!("invalid type: null, expected {exp}"))
251        } else {
252            Self::custom(format_args!("invalid type: {unexp}, expected {exp}"))
253        }
254    }
255}
256
257impl ser::Error for Error {
258    fn custom<T: fmt::Display>(msg: T) -> Self {
259        Self::message(msg)
260    }
261}
262
263#[cfg(feature = "std")]
264impl From<io::Error> for Error {
265    fn from(e: io::Error) -> Self {
266        Self::io(e)
267    }
268}
269
270#[cfg(not(feature = "std"))]
271impl From<core::fmt::Error> for Error {
272    fn from(_: core::fmt::Error) -> Self {
273        Self(ErrorImpl {
274            code: ErrorCode::Message,
275            offset: 0,
276        })
277    }
278}
279
280#[derive(Debug)]
281struct ErrorImpl {
282    code: ErrorCode,
283    offset: u64,
284    #[cfg(feature = "std")]
285    _backtrace: std::backtrace::Backtrace,
286}
287
288#[derive(Debug)]
289pub(crate) enum ErrorCode {
290    #[cfg(feature = "std")]
291    Message(String),
292    #[cfg(not(feature = "std"))]
293    Message,
294    #[cfg(feature = "std")]
295    Io(io::Error),
296    #[allow(unused)]
297    #[cfg(not(feature = "std"))]
298    Io,
299    ScratchTooSmall,
300    EofWhileParsingValue,
301    EofWhileParsingArray,
302    EofWhileParsingMap,
303    LengthOutOfRange,
304    InvalidUtf8,
305    UnassignedCode,
306    UnexpectedCode,
307    TrailingData,
308    ArrayTooShort,
309    ArrayTooLong,
310    RecursionLimitExceeded,
311    WrongEnumFormat,
312    WrongStructFormat,
313    ArraySizeLimitExceeded,
314    MapSizeLimitExceeded,
315    IndefiniteIterationLimitExceeded,
316}
317
318impl fmt::Display for ErrorCode {
319    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320        match *self {
321            #[cfg(feature = "std")]
322            Self::Message(ref msg) => f.write_str(msg),
323            #[cfg(not(feature = "std"))]
324            Self::Message => f.write_str("Unknown error"),
325            #[cfg(feature = "std")]
326            Self::Io(ref err) => fmt::Display::fmt(err, f),
327            #[cfg(not(feature = "std"))]
328            Self::Io => f.write_str("Unknown I/O error"),
329            Self::ScratchTooSmall => f.write_str("Scratch buffer too small"),
330            Self::EofWhileParsingValue => f.write_str("EOF while parsing a value"),
331            Self::EofWhileParsingArray => f.write_str("EOF while parsing an array"),
332            Self::EofWhileParsingMap => f.write_str("EOF while parsing a map"),
333            Self::LengthOutOfRange => f.write_str("length out of range"),
334            Self::InvalidUtf8 => f.write_str("invalid UTF-8"),
335            Self::UnassignedCode => f.write_str("unassigned type"),
336            Self::UnexpectedCode => f.write_str("unexpected code"),
337            Self::TrailingData => f.write_str("trailing data"),
338            Self::ArrayTooShort => f.write_str("array too short"),
339            Self::ArrayTooLong => f.write_str("array too long"),
340            Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
341            Self::WrongEnumFormat => f.write_str("wrong enum format"),
342            Self::WrongStructFormat => f.write_str("wrong struct format"),
343            Self::ArraySizeLimitExceeded => f.write_str("array size limit exceeded"),
344            Self::MapSizeLimitExceeded => f.write_str("map size limit exceeded"),
345            Self::IndefiniteIterationLimitExceeded => {
346                f.write_str("indefinite-length iteration limit exceeded")
347            }
348        }
349    }
350}