binrw/error/
mod.rs

1//! Functions and type definitions for handling errors.
2
3mod backtrace;
4
5use crate::{io, BinResult};
6use alloc::borrow::Cow;
7#[cfg(not(feature = "std"))]
8use alloc::{boxed::Box, string::String, vec, vec::Vec};
9pub use backtrace::*;
10use core::{any::Any, fmt};
11
12/// The `ContextExt` trait allows extra information to be added to errors.
13///
14/// This is used to add tracking information to errors that bubble up from an
15/// inner field.
16pub trait ContextExt {
17    /// Adds a new context frame to the error, consuming the original error.
18    #[must_use]
19    fn with_context<Frame: Into<BacktraceFrame>>(self, frame: Frame) -> Self;
20
21    /// Adds a new frame of context to the error with the given message,
22    /// consuming the original error.
23    ///
24    /// This also adds the file name and line number of the caller to the error.
25    #[must_use]
26    #[track_caller]
27    fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self;
28}
29
30impl ContextExt for Error {
31    fn with_context<Frame: Into<BacktraceFrame>>(self, frame: Frame) -> Self {
32        match self {
33            Error::Backtrace(mut backtrace) => {
34                backtrace.frames.push(frame.into());
35                Error::Backtrace(backtrace)
36            }
37            error => Error::Backtrace(Backtrace::new(error, vec![frame.into()])),
38        }
39    }
40
41    #[track_caller]
42    fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
43        match self {
44            Error::Backtrace(backtrace) => Error::Backtrace(backtrace.with_message(message)),
45            error => {
46                let caller = core::panic::Location::caller();
47                Error::Backtrace(Backtrace::new(
48                    error,
49                    vec![BacktraceFrame::Full {
50                        code: None,
51                        message: message.into(),
52                        file: caller.file(),
53                        line: caller.line(),
54                    }],
55                ))
56            }
57        }
58    }
59}
60
61impl<T> ContextExt for BinResult<T> {
62    fn with_context<Frame: Into<BacktraceFrame>>(self, frame: Frame) -> Self {
63        self.map_err(|err| err.with_context(frame))
64    }
65
66    #[track_caller]
67    fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
68        match self {
69            Err(err) => {
70                let caller = core::panic::Location::caller();
71                Err(match err {
72                    Error::Backtrace(backtrace) => {
73                        Error::Backtrace(backtrace.with_message(message))
74                    }
75                    error => Error::Backtrace(Backtrace::new(
76                        error,
77                        vec![BacktraceFrame::Full {
78                            code: None,
79                            message: message.into(),
80                            file: caller.file(),
81                            line: caller.line(),
82                        }],
83                    )),
84                })
85            }
86            ok => ok,
87        }
88    }
89}
90
91/// The `CustomError` trait describes types that are usable as custom errors
92/// in a [`BinResult`].
93///
94/// This trait is automatically implemented for any type which implements the
95/// same traits as [`std::error::Error`], so anything you would normally use as
96/// an error in other code is also a valid `CustomError`, with the additional
97/// restriction that it must also be [`Send`] + [`Sync`].
98///
99/// This trait is Sealed.
100pub trait CustomError: fmt::Display + fmt::Debug + Send + Sync + private::Sealed {
101    #[doc(hidden)]
102    fn as_any(&self) -> &(dyn Any + Send + Sync);
103
104    #[doc(hidden)]
105    fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync);
106
107    #[doc(hidden)]
108    fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
109}
110
111impl<T: fmt::Display + fmt::Debug + Send + Sync + 'static> CustomError for T {
112    fn as_any(&self) -> &(dyn Any + Send + Sync) {
113        self
114    }
115
116    fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) {
117        self
118    }
119
120    fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
121        self
122    }
123}
124
125// The intent here is to allow any object which is compatible with
126// `std::error::Error + Send + Sync` to be stored in errors, including no_std
127// mode.
128impl dyn CustomError {
129    /// Attempts to downcast a boxed error to a concrete type.
130    ///
131    /// # Errors
132    ///
133    /// If the downcast fails, `Self` will be returned.
134    // Lint: Does not panic; the unwrap will not fail due to the `is`-guard and
135    // must be expressed this way due to borrowck limitations
136    #[allow(clippy::missing_panics_doc)]
137    pub fn downcast<T: CustomError + 'static>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
138        if self.is::<T>() {
139            Ok(self.as_box_any().downcast().unwrap())
140        } else {
141            Err(self)
142        }
143    }
144
145    /// Returns some mutable reference to the boxed value if it is of type `T`, or
146    /// `None` if it isn't.
147    pub fn downcast_mut<T: CustomError + 'static>(&mut self) -> Option<&mut T> {
148        self.as_any_mut().downcast_mut()
149    }
150
151    /// Returns some reference to the boxed value if it is of type `T`, or
152    /// `None` if it isn’t.
153    pub fn downcast_ref<T: CustomError + 'static>(&self) -> Option<&T> {
154        self.as_any().downcast_ref()
155    }
156
157    /// Returns `true` if the boxed type is the same as `T`.
158    pub fn is<T: CustomError + 'static>(&self) -> bool {
159        core::any::TypeId::of::<T>() == self.as_any().type_id()
160    }
161}
162
163/// The error type used by [`BinRead`](crate::BinRead).
164#[non_exhaustive]
165pub enum Error {
166    /// An expected [magic number](crate::docs::attribute#magic) was not found.
167    BadMagic {
168        /// The byte position of the unexpected magic in the reader.
169        pos: u64,
170
171        /// The value which was actually read.
172        found: Box<dyn fmt::Debug + Send + Sync>,
173    },
174
175    /// An assertion failed.
176    ///
177    /// This variant is used for [`assert`] directives which use a string
178    /// literal instead of an error object. Assertions that use error objects
179    /// are represented by the [`Custom`] variant.
180    ///
181    /// [`assert`]: crate::docs::attribute#assert
182    /// [`Custom`]: Self::Custom
183    AssertFail {
184        /// The byte position of the start of the field or object that raised
185        /// an error.
186        pos: u64,
187
188        /// The failure message.
189        message: String,
190    },
191
192    /// An error occurred in the underlying reader while reading or seeking to
193    /// data.
194    Io(io::Error),
195
196    /// A user-generated error.
197    ///
198    /// This variant is used for [`assert`] directives which use an error object
199    /// instead of a string literal. Assertions that use string literals are
200    /// represented by the [`AssertFail`] variant.
201    ///
202    /// [`assert`]: crate::docs::attribute#assert
203    /// [`AssertFail`]: Self::AssertFail
204    Custom {
205        /// The byte position of the start of the field or object that raised
206        /// an error.
207        pos: u64,
208
209        /// The original error.
210        err: Box<dyn CustomError>,
211    },
212
213    /// None of the variants of an enum could successfully be parsed from the
214    /// data in the reader.
215    ///
216    /// This variant is used when the [`return_unexpected_error`] directive is
217    /// set on an enum.
218    ///
219    /// [`return_unexpected_error`]: crate::docs::attribute#enum-errors
220    NoVariantMatch {
221        /// The byte position of the unparsable data in the reader.
222        pos: u64,
223    },
224
225    /// None of the variants of an enum could successfully be parsed from the
226    /// data in the reader.
227    ///
228    /// This variant is used when the [`return_all_errors`] directive is
229    /// set on an enum (which is the default).
230    ///
231    /// [`return_all_errors`]: crate::docs::attribute#enum-errors
232    EnumErrors {
233        /// The byte position of the unparsable data in the reader.
234        pos: u64,
235
236        /// The original errors which occurred when trying to parse each
237        /// variant.
238        ///
239        /// The first field of the tuple is the name of the variant, and the
240        /// second field is the error that occurred when parsing that variant.
241        variant_errors: Vec<(&'static str, Error)>,
242    },
243
244    /// An error with additional frames of context used to construct a backtrace
245    Backtrace(Backtrace),
246}
247
248impl Error {
249    /// Returns the source error. For a Backtrace this is the error that caused it, for every
250    /// other error this returns self
251    #[must_use]
252    pub fn root_cause(&self) -> &Self {
253        match self {
254            Self::Backtrace(backtrace) => &backtrace.error,
255            error => error,
256        }
257    }
258
259    /// Check if the [root cause][`Self::root_cause`] of this error is an [`Error::Io`] and an
260    /// [`io::ErrorKind::UnexpectedEof`].
261    #[must_use]
262    pub fn is_eof(&self) -> bool {
263        match self {
264            Error::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof => true,
265            Error::EnumErrors { variant_errors, .. } => {
266                variant_errors.iter().all(|(_, err)| err.is_eof())
267            }
268            Error::Backtrace(bt) => bt.error.is_eof(),
269            _ => false,
270        }
271    }
272
273    /// Returns a reference to the boxed error object if this `Error` is a
274    /// custom error of type `T`, or `None` if it isn’t.
275    #[must_use]
276    pub fn custom_err<T: CustomError + 'static>(&self) -> Option<&T> {
277        if let Error::Custom { err, .. } = self.root_cause() {
278            err.downcast_ref()
279        } else {
280            None
281        }
282    }
283}
284
285impl From<io::Error> for Error {
286    fn from(err: io::Error) -> Self {
287        Self::Io(err)
288    }
289}
290
291impl fmt::Display for Error {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        match self {
294            Self::BadMagic { pos, found } => write!(f, "bad magic at 0x{pos:x}: {found:?}"),
295            Self::AssertFail { pos, message } => write!(f, "{message} at 0x{pos:x}"),
296            Self::Io(err) => fmt::Display::fmt(err, f),
297            Self::Custom { pos, err } => write!(f, "{err} at 0x{pos:x}"),
298            Self::NoVariantMatch { pos } => write!(f, "no variants matched at 0x{pos:x}"),
299            Self::EnumErrors {
300                pos,
301                variant_errors,
302            } => {
303                write!(f, "no variants matched at 0x{pos:x}:")?;
304                for (name, err) in variant_errors {
305                    write!(f, "\n  {name}: {err}")?;
306                }
307                Ok(())
308            }
309            Self::Backtrace(backtrace) => fmt::Display::fmt(backtrace, f),
310        }
311    }
312}
313
314impl fmt::Debug for Error {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        <Error as fmt::Display>::fmt(self, f)
317    }
318}
319
320#[cfg(feature = "std")]
321impl std::error::Error for Error {}
322
323mod private {
324    use core::fmt;
325    pub trait Sealed {}
326    impl<T: fmt::Display + fmt::Debug + Send + Sync + 'static> Sealed for T {}
327}