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}