Skip to main content

ender/
error.rs

1use crate::source::Stack;
2use crate::{BitWidth, Endianness, NumEncoding, Opaque, StrEncoding, StrLen};
3use core::fmt;
4use core::fmt::Formatter;
5use embedded_io::{Error, ErrorKind, ReadExactError};
6use parse_display::Display;
7
8macro_rules! impl_error {
9    ($name:ident) => {
10        #[cfg(feature = "unstable")]
11        impl core::error::Error for $name {}
12
13        #[cfg(all(not(feature = "unstable"), feature = "std"))]
14        impl std::error::Error for $name {}
15    };
16}
17
18/// The signedness of an integer value - whether it can store negative numbers.
19///
20/// E.G. everything `u*` is `Unsigned`, everything `i*` is `Signed`.
21///
22/// This enum is provided for diagnostic purposes in [`EncodingError::SignMismatch`]
23#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
24pub enum Signedness {
25    Signed,
26    Unsigned,
27}
28
29/// Represents any kind of error that can happen during encoding and decoding
30#[derive(Debug, Display)]
31#[non_exhaustive]
32pub enum EncodingError {
33    /// Generic IO error
34    #[display("IO Error occurred: {:0?}")]
35    IOError(ErrorKind),
36    /// The end of the file or buffer was reached but more data was expected
37    #[display("Unexpected end of file/buffer")]
38    UnexpectedEnd,
39    /// An error involving the [`seek`][`crate::io::Seek::seek`] operation occurred.
40    #[display("Seek error: {0}")]
41    SeekError(SeekError),
42    /// A var-int was malformed and could not be decoded
43    #[display("Malformed var-int encoding")]
44    VarIntError,
45    /// A value other than `1` or `0` was read while decoding a `bool`
46    #[display("Invalid bool value")]
47    InvalidBool,
48    /// An attempt was made to encode or decode a string, but *something* went wrong.
49    #[display("String error: {0}")]
50    StringError(StringError),
51    /// Tried to write or read a `usize` greater than the max
52    #[display("A size of {requested} exceeded the max allowed value of {max}")]
53    MaxSizeExceeded { max: usize, requested: usize },
54    /// Tried to decode an unrecognized enum variant
55    #[display("Unrecognized enum variant ({0})")]
56    InvalidVariant(Opaque),
57    /// Tried to squeeze a value into fewer bits than what is required to fully represent it.
58    #[display(r#"A value of "{value}" is too large to fit in {requested_width}"#)]
59    TooLarge {
60        value: Opaque,
61        requested_width: BitWidth,
62    },
63    /// Expected to get a value with a specific signedness, but got one with the opposite.
64    /// E.G. Expected an `u8` but got an `i8`.
65    #[display("Expected {expected} value, got {got} value instead")]
66    SignMismatch {
67        expected: Signedness,
68        got: Signedness,
69    },
70    /// An attempt was made to flatten an option or result, but the inner value was unexpected.
71    /// Example: `#[ender(flatten: some)]` applied on an `Option` containing the `None` variant
72    #[display("Flatten error: {0}")]
73    FlattenError(FlattenError),
74    /// An attempt was made to lock a RefCell/Mutex/RwLock or similar, but it failed.
75    #[display("Lock error: couldn't lock a RefCell/Mutex/RwLock or similar")]
76    LockError,
77    /// A piece of data couldn't be borrowed from the encoder. This is a recoverable error,
78    /// meaning the decoding operation can be attempted again with a non-borrowing function.
79    #[display("Borrow error: {0}")]
80    BorrowError(BorrowError),
81    /// A `#[ender(validate = ...)]` check failed
82    #[display("Validation error: {0}")]
83    ValidationError(
84        #[cfg(feature = "alloc")] alloc::string::String,
85        #[cfg(not(feature = "alloc"))] &'static str,
86    ),
87    /// A generic serde error occurred
88    #[cfg(all(feature = "serde", feature = "alloc"))]
89    #[cfg_attr(feature = "unstable", doc(cfg(feature = "serde")))]
90    #[display("Serde error: {0}")]
91    SerdeError(alloc::string::String),
92    /// A generic serde error occurred
93    #[cfg(all(feature = "serde", not(feature = "alloc")))]
94    #[cfg_attr(feature = "unstable", doc(cfg(feature = "serde")))]
95    #[display("Serde error")]
96    SerdeError,
97}
98
99#[macro_export]
100macro_rules! val_error {
101    ($($tt:tt)*) => {{
102        $crate::EncodingError::validation_error(::core::format_args!($($tt)*))
103    }};
104}
105
106impl EncodingError {
107    pub fn validation_error<'a>(fmt: fmt::Arguments<'a>) -> Self {
108        #[cfg(feature = "alloc")]
109        #[allow(unused_imports)]
110        {
111            use alloc::string::ToString;
112            Self::ValidationError(fmt.to_string())
113        }
114        #[cfg(not(feature = "alloc"))]
115        {
116            if let Some(str) = fmt.as_str() {
117                Self::ValidationError(str)
118            } else {
119                Self::ValidationError("Unknown")
120            }
121        }
122    }
123
124    pub fn invalid_variant<V>(v: V) -> Self
125    where
126        Opaque: From<V>,
127    {
128        Self::InvalidVariant(Opaque::from(v))
129    }
130}
131
132impl Error for EncodingError {
133    fn kind(&self) -> ErrorKind {
134        match self {
135            EncodingError::IOError(io_error) => io_error.kind().into(),
136            EncodingError::UnexpectedEnd => ErrorKind::Other,
137            EncodingError::FlattenError(_) => ErrorKind::InvalidInput,
138            EncodingError::LockError => ErrorKind::Other,
139            EncodingError::BorrowError(_) => ErrorKind::Other,
140            #[cfg(all(feature = "serde", feature = "alloc"))]
141            EncodingError::SerdeError(_) => ErrorKind::Other,
142            #[cfg(all(feature = "serde", not(feature = "alloc")))]
143            EncodingError::SerdeError => ErrorKind::Other,
144            _ => ErrorKind::InvalidData,
145        }
146    }
147}
148
149impl_error!(EncodingError);
150
151#[cfg(feature = "std")]
152impl From<std::io::Error> for EncodingError {
153    fn from(value: std::io::Error) -> Self {
154        match value.kind() {
155            std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEnd,
156            kind @ _ => Self::IOError(kind.into()),
157        }
158    }
159}
160
161impl From<ErrorKind> for EncodingError {
162    fn from(value: ErrorKind) -> Self {
163        Self::IOError(value)
164    }
165}
166
167impl<T: Error> From<ReadExactError<T>> for EncodingError {
168    fn from(value: ReadExactError<T>) -> Self {
169        match value {
170            ReadExactError::UnexpectedEof => Self::UnexpectedEnd,
171            ReadExactError::Other(io_error) => Self::IOError(io_error.kind().into()),
172        }
173    }
174}
175
176impl From<StringError> for EncodingError {
177    fn from(value: StringError) -> Self {
178        Self::StringError(value)
179    }
180}
181
182impl From<FlattenError> for EncodingError {
183    fn from(value: FlattenError) -> Self {
184        Self::FlattenError(value)
185    }
186}
187
188impl From<BorrowError> for EncodingError {
189    fn from(value: BorrowError) -> Self {
190        Self::BorrowError(value)
191    }
192}
193
194impl From<SeekError> for EncodingError {
195    fn from(value: SeekError) -> Self {
196        Self::SeekError(value)
197    }
198}
199
200/// Represents an error occurred while encoding or decoding a string, including intermediate
201/// conversion errors and the presence of null bytes in unexpected scenarios.
202#[derive(Debug, Display)]
203#[non_exhaustive]
204pub enum StringError {
205    /// A generic conversion error. E.G. converting an `OsStr` to `str` and back
206    #[display("String conversion error")]
207    ConversionError,
208    /// A string contained invalid data
209    #[display("Invalid characters in string data")]
210    InvalidChar,
211    /// A string (encoded using [`StrLen::NullTerminatedOrMax`]) exceeded the maximum length
212    #[display("String is longer than expected")]
213    TooLong,
214    /// A string (encoded using [`StrLen::NullTerminatedOrMax`]) contained a null byte followed
215    /// by an unexpected non-null byte before the end of the string data.
216    #[display("Null byte expected")]
217    MissingNull,
218}
219
220impl_error!(StringError);
221
222/// Represents an error related to the "flatten" functionality, with potentially useful diagnostics
223#[derive(Debug, Display)]
224#[non_exhaustive]
225pub enum FlattenError {
226    #[display("Enum discriminant mismatch: expected {expected}, got {got}")]
227    VariantMismatch { expected: Opaque, got: Opaque },
228    #[display("Boolean mismatch: expected {expected}, got {got}")]
229    BoolMismatch { expected: bool, got: bool },
230    #[display("Length mismatch: expected {expected}, got {got}")]
231    LenMismatch { expected: usize, got: usize },
232}
233
234impl_error!(FlattenError);
235
236#[derive(Debug, Display)]
237#[non_exhaustive]
238pub enum BorrowError {
239    #[display(
240        "String encoding mismatch: expected {found} while decoding a {while_decoding} string"
241    )]
242    StrEncodingMismatch {
243        found: StrEncoding,
244        while_decoding: StrEncoding,
245    },
246    #[display(
247        "String length encoding mismatch: expected {found} while decoding a {while_decoding} string"
248    )]
249    StrLenEncodingMismatch {
250        found: StrLen,
251        while_decoding: StrLen,
252    },
253    #[display("Endianness mismatch: stream contains {found} data, but system uses {system}")]
254    EndiannessMismatch {
255        found: Endianness,
256        system: Endianness,
257    },
258    #[display("Bit width mismatch: stream contains {found} data, but system uses {system}")]
259    BitWidthMismatch { found: BitWidth, system: BitWidth },
260    #[display("Non-borrowable numerical encoding: {num_encoding} can't be directly borrowed")]
261    NonBorrowableNumEncoding { num_encoding: NumEncoding },
262    #[display(
263        "Alignment mismatch: borrowing this data requires its alignment to match the system's"
264    )]
265    AlignmentMismatch,
266}
267
268impl_error!(BorrowError);
269
270#[derive(Debug, Display)]
271#[non_exhaustive]
272pub enum SeekError {
273    #[display("Tried to seek to a negative offset: {0}")]
274    BeforeBeginning(isize),
275    #[display("Tried to seek to an offset beyond the end: {0}")]
276    AfterEnd(usize),
277    #[display("Tried to seek to the beginning/end but they are unknown")]
278    UnknownRange,
279}
280
281impl_error!(SeekError);
282
283/// An [`EncodingError`] which also displays all the error stack.
284/// This is useful for debugging, because the entire structure tree is displayed.
285#[derive(Debug)]
286pub struct TaggedError {
287    err: EncodingError,
288    stack: Stack,
289}
290
291impl core::fmt::Display for TaggedError {
292    #[inline]
293    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
294        #[cfg(feature = "alloc")]
295        {
296            if self.stack.frames.is_empty() {
297                return write!(f, "{}", self.err);
298            }
299
300            write!(f, "[{}] {}", self.stack, self.err)?;
301        }
302        #[cfg(not(feature = "alloc"))]
303        {
304            if let crate::source::Frame::Item(x) = self.stack.last_frame {
305                if x == "" {
306                    return write!(f, "{}", self.err);
307                }
308            }
309
310            write!(f, "[{}] {}", self.stack, self.err)?;
311        }
312        Ok(())
313    }
314}
315
316impl TaggedError {
317    /// Constructs a new [`TaggedError`] from an encoding error and an
318    /// error metadata stack.
319    ///
320    /// If the `debug` feature is enabled, you can obtain an instance of [`Stack`]
321    /// from an [`Encoder`] right after an error occurred (`encoder.stack`).
322    #[inline]
323    pub const fn from_stack(err: EncodingError, stack: Stack) -> Self {
324        Self { err, stack }
325    }
326}
327
328/// A convenience alias to `Result<T, EncodingError>`
329pub type EncodingResult<T> = Result<T, EncodingError>;