| use std::borrow::Borrow; |
| use std::borrow::Cow; |
| use std::error; |
| use std::error::Error as _; |
| use std::fmt::Debug; |
| use std::fmt::Display; |
| use std::fmt::Formatter; |
| use std::fmt::Result as FmtResult; |
| use std::io; |
| use std::mem::transmute; |
| use std::ops::Deref; |
| use std::result; |
| |
| /// A result type using our [`Error`] by default. |
| pub type Result<T, E = Error> = result::Result<T, E>; |
| |
| #[allow(clippy::wildcard_imports)] |
| mod private { |
| use super::*; |
| |
| pub trait Sealed {} |
| |
| impl<T> Sealed for Option<T> {} |
| impl<T, E> Sealed for Result<T, E> {} |
| impl Sealed for &'static str {} |
| impl Sealed for String {} |
| impl Sealed for Error {} |
| |
| impl Sealed for io::Error {} |
| } |
| |
| /// A `str` replacement whose owned representation is a `Box<str>` and |
| /// not a `String`. |
| #[derive(Debug)] |
| #[repr(transparent)] |
| #[doc(hidden)] |
| pub struct Str(str); |
| |
| impl ToOwned for Str { |
| type Owned = Box<str>; |
| |
| #[inline] |
| fn to_owned(&self) -> Self::Owned { |
| self.0.to_string().into_boxed_str() |
| } |
| } |
| |
| impl Borrow<Str> for Box<str> { |
| #[inline] |
| fn borrow(&self) -> &Str { |
| // SAFETY: `Str` is `repr(transparent)` and so `&str` and `&Str` |
| // can trivially be converted into each other. |
| unsafe { transmute::<&str, &Str>(self.deref()) } |
| } |
| } |
| |
| impl Deref for Str { |
| type Target = str; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| |
| // For convenient use in `format!`, for example. |
| impl Display for Str { |
| #[inline] |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| Display::fmt(&self.0, f) |
| } |
| } |
| |
| /// A helper trait to abstracting over various string types, allowing |
| /// for conversion into a `Cow<'static, Str>`. This is the `Cow` enabled |
| /// equivalent of `ToString`. |
| pub trait IntoCowStr: private::Sealed { |
| fn into_cow_str(self) -> Cow<'static, Str>; |
| } |
| |
| impl IntoCowStr for &'static str { |
| fn into_cow_str(self) -> Cow<'static, Str> { |
| // SAFETY: `Str` is `repr(transparent)` and so `&str` and `&Str` |
| // can trivially be converted into each other. |
| let other = unsafe { transmute::<&str, &Str>(self) }; |
| Cow::Borrowed(other) |
| } |
| } |
| |
| impl IntoCowStr for String { |
| fn into_cow_str(self) -> Cow<'static, Str> { |
| Cow::Owned(self.into_boxed_str()) |
| } |
| } |
| |
| // TODO: We may want to support optionally storing a backtrace in |
| // terminal variants. |
| enum ErrorImpl { |
| Io(io::Error), |
| // Unfortunately, if we just had a single `Context` variant that |
| // contains a `Cow`, this inner `Cow` would cause an overall enum |
| // size increase by a machine word, because currently `rustc` |
| // seemingly does not fold the necessary bits into the outer enum. |
| // We have two variants to work around that until `rustc` is smart |
| // enough. |
| ContextOwned { |
| context: Box<str>, |
| source: Box<ErrorImpl>, |
| }, |
| ContextStatic { |
| context: &'static str, |
| source: Box<ErrorImpl>, |
| }, |
| } |
| |
| impl ErrorImpl { |
| fn kind(&self) -> ErrorKind { |
| match self { |
| Self::Io(error) => match error.kind() { |
| io::ErrorKind::NotFound => ErrorKind::NotFound, |
| io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied, |
| io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, |
| io::ErrorKind::WouldBlock => ErrorKind::WouldBlock, |
| io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, |
| io::ErrorKind::InvalidData => ErrorKind::InvalidData, |
| io::ErrorKind::TimedOut => ErrorKind::TimedOut, |
| io::ErrorKind::WriteZero => ErrorKind::WriteZero, |
| io::ErrorKind::Interrupted => ErrorKind::Interrupted, |
| io::ErrorKind::Unsupported => ErrorKind::Unsupported, |
| io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, |
| io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory, |
| _ => ErrorKind::Other, |
| }, |
| Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => { |
| source.deref().kind() |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| fn is_owned(&self) -> Option<bool> { |
| match self { |
| Self::ContextOwned { .. } => Some(true), |
| Self::ContextStatic { .. } => Some(false), |
| _ => None, |
| } |
| } |
| } |
| |
| impl Debug for ErrorImpl { |
| // We try to mirror roughly how anyhow's Error is behaving, because |
| // that makes the most sense. |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| if f.alternate() { |
| let mut dbg; |
| |
| match self { |
| Self::Io(io) => { |
| dbg = f.debug_tuple(stringify!(Io)); |
| dbg.field(io) |
| } |
| Self::ContextOwned { context, .. } => { |
| dbg = f.debug_tuple(stringify!(ContextOwned)); |
| dbg.field(context) |
| } |
| Self::ContextStatic { context, .. } => { |
| dbg = f.debug_tuple(stringify!(ContextStatic)); |
| dbg.field(context) |
| } |
| } |
| .finish() |
| } else { |
| let () = match self { |
| Self::Io(error) => write!(f, "Error: {error}")?, |
| Self::ContextOwned { context, .. } => write!(f, "Error: {context}")?, |
| Self::ContextStatic { context, .. } => write!(f, "Error: {context}")?, |
| }; |
| |
| if let Some(source) = self.source() { |
| let () = f.write_str("\n\nCaused by:")?; |
| |
| let mut error = Some(source); |
| while let Some(err) = error { |
| let () = write!(f, "\n {err:}")?; |
| error = err.source(); |
| } |
| } |
| Ok(()) |
| } |
| } |
| } |
| |
| impl Display for ErrorImpl { |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| let () = match self { |
| Self::Io(error) => Display::fmt(error, f)?, |
| Self::ContextOwned { context, .. } => Display::fmt(context, f)?, |
| Self::ContextStatic { context, .. } => Display::fmt(context, f)?, |
| }; |
| |
| if f.alternate() { |
| let mut error = self.source(); |
| while let Some(err) = error { |
| let () = write!(f, ": {err}")?; |
| error = err.source(); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| impl error::Error for ErrorImpl { |
| fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
| match self { |
| Self::Io(error) => error.source(), |
| Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => Some(source), |
| } |
| } |
| } |
| |
| /// An enum providing a rough classification of errors. |
| /// |
| /// The variants of this type partly resemble those of |
| /// [`std::io::Error`], because these are the most common sources of |
| /// error that the crate concerns itself with. |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| #[non_exhaustive] |
| pub enum ErrorKind { |
| /// An entity was not found, often a file. |
| NotFound, |
| /// The operation lacked the necessary privileges to complete. |
| PermissionDenied, |
| /// An entity already exists, often a file. |
| AlreadyExists, |
| /// The operation needs to block to complete, but the blocking |
| /// operation was requested to not occur. |
| WouldBlock, |
| /// A parameter was incorrect. |
| InvalidInput, |
| /// Data not valid for the operation were encountered. |
| InvalidData, |
| /// The I/O operation's timeout expired, causing it to be canceled. |
| TimedOut, |
| /// An error returned when an operation could not be completed |
| /// because a call to [`write`] returned [`Ok(0)`]. |
| WriteZero, |
| /// This operation was interrupted. |
| /// |
| /// Interrupted operations can typically be retried. |
| Interrupted, |
| /// This operation is unsupported on this platform. |
| Unsupported, |
| /// An error returned when an operation could not be completed |
| /// because an "end of file" was reached prematurely. |
| UnexpectedEof, |
| /// An operation could not be completed, because it failed |
| /// to allocate enough memory. |
| OutOfMemory, |
| /// A custom error that does not fall under any other I/O error |
| /// kind. |
| Other, |
| } |
| |
| /// The error type used by the library. |
| /// |
| /// Errors generally form a chain, with higher-level errors typically |
| /// providing additional context for lower level ones. E.g., an IO error |
| /// such as file-not-found could be reported by a system level API (such |
| /// as [`std::fs::File::open`]) and may be contextualized with the path |
| /// to the file attempted to be opened. |
| /// |
| /// ``` |
| /// use std::fs::File; |
| /// use std::error::Error as _; |
| /// # use libbpf_rs::ErrorExt as _; |
| /// |
| /// let path = "/does-not-exist"; |
| /// let result = File::open(path).with_context(|| format!("failed to open {path}")); |
| /// |
| /// let err = result.unwrap_err(); |
| /// assert_eq!(err.to_string(), "failed to open /does-not-exist"); |
| /// |
| /// // Retrieve the underlying error. |
| /// let inner_err = err.source().unwrap(); |
| /// assert!(inner_err.to_string().starts_with("No such file or directory")); |
| /// ``` |
| /// |
| /// For convenient reporting, the [`Display`][std::fmt::Display] |
| /// representation takes care of reporting the complete error chain when |
| /// the alternate flag is set: |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::error::Error as _; |
| /// # use libbpf_rs::ErrorExt as _; |
| /// # let path = "/does-not-exist"; |
| /// # let result = File::open(path).with_context(|| format!("failed to open {path}")); |
| /// # let err = result.unwrap_err(); |
| /// // > failed to open /does-not-exist: No such file or directory (os error 2) |
| /// println!("{err:#}"); |
| /// ``` |
| /// |
| /// The [`Debug`][std::fmt::Debug] representation similarly will print |
| /// the entire error chain, but will do so in a multi-line format: |
| /// ``` |
| /// # use std::fs::File; |
| /// # use std::error::Error as _; |
| /// # use libbpf_rs::ErrorExt as _; |
| /// # let path = "/does-not-exist"; |
| /// # let result = File::open(path).with_context(|| format!("failed to open {path}")); |
| /// # let err = result.unwrap_err(); |
| /// // > Error: failed to open /does-not-exist |
| /// // > |
| /// // > Caused by: |
| /// // > No such file or directory (os error 2) |
| /// println!("{err:?}"); |
| /// ``` |
| // Representation is optimized for fast copying (a single machine word), |
| // not so much for fast creation (as it is heap allocated). We generally |
| // expect errors to be exceptional, though a lot of functionality is |
| // fallible (i.e., returns a `Result<T, Error>` which would be penalized |
| // by a large `Err` variant). |
| #[repr(transparent)] |
| pub struct Error { |
| /// The top-most error of the chain. |
| error: Box<ErrorImpl>, |
| } |
| |
| impl Error { |
| /// Create an [`Error`] from an OS error code (typically `errno`). |
| /// |
| /// # Notes |
| /// An OS error code should always be positive. |
| #[inline] |
| pub fn from_raw_os_error(code: i32) -> Self { |
| debug_assert!( |
| code > 0, |
| "OS error code should be positive integer; got: {code}" |
| ); |
| Self::from(io::Error::from_raw_os_error(code)) |
| } |
| |
| #[inline] |
| pub(crate) fn with_io_error<E>(kind: io::ErrorKind, error: E) -> Self |
| where |
| E: ToString, |
| { |
| Self::from(io::Error::new(kind, error.to_string())) |
| } |
| |
| #[inline] |
| pub(crate) fn with_invalid_data<E>(error: E) -> Self |
| where |
| E: ToString, |
| { |
| Self::with_io_error(io::ErrorKind::InvalidData, error) |
| } |
| |
| /// Retrieve a rough error classification in the form of an |
| /// [`ErrorKind`]. |
| #[inline] |
| pub fn kind(&self) -> ErrorKind { |
| self.error.kind() |
| } |
| |
| /// Layer the provided context on top of this `Error`, creating a |
| /// new one in the process. |
| fn layer_context(self, context: Cow<'static, Str>) -> Self { |
| match context { |
| Cow::Owned(context) => Self { |
| error: Box::new(ErrorImpl::ContextOwned { |
| context, |
| source: self.error, |
| }), |
| }, |
| Cow::Borrowed(context) => Self { |
| error: Box::new(ErrorImpl::ContextStatic { |
| context, |
| source: self.error, |
| }), |
| }, |
| } |
| } |
| } |
| |
| impl Debug for Error { |
| #[inline] |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| Debug::fmt(&self.error, f) |
| } |
| } |
| |
| impl Display for Error { |
| #[inline] |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| Display::fmt(&self.error, f) |
| } |
| } |
| |
| impl error::Error for Error { |
| #[inline] |
| fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
| self.error.source() |
| } |
| } |
| |
| impl From<io::Error> for Error { |
| fn from(other: io::Error) -> Self { |
| Self { |
| error: Box::new(ErrorImpl::Io(other)), |
| } |
| } |
| } |
| |
| /// A trait providing ergonomic chaining capabilities to [`Error`]. |
| pub trait ErrorExt: private::Sealed { |
| /// The output type produced by [`context`](Self::context) and |
| /// [`with_context`](Self::with_context). |
| type Output; |
| |
| /// Add context to this error. |
| // If we had specialization of sorts we could be more lenient as to |
| // what we can accept, but for now this method always works with |
| // static strings and nothing else. |
| fn context<C>(self, context: C) -> Self::Output |
| where |
| C: IntoCowStr; |
| |
| /// Add context to this error, using a closure for lazy evaluation. |
| fn with_context<C, F>(self, f: F) -> Self::Output |
| where |
| C: IntoCowStr, |
| F: FnOnce() -> C; |
| } |
| |
| impl ErrorExt for Error { |
| type Output = Error; |
| |
| fn context<C>(self, context: C) -> Self::Output |
| where |
| C: IntoCowStr, |
| { |
| self.layer_context(context.into_cow_str()) |
| } |
| |
| fn with_context<C, F>(self, f: F) -> Self::Output |
| where |
| C: IntoCowStr, |
| F: FnOnce() -> C, |
| { |
| self.layer_context(f().into_cow_str()) |
| } |
| } |
| |
| impl<T, E> ErrorExt for Result<T, E> |
| where |
| E: ErrorExt, |
| { |
| type Output = Result<T, E::Output>; |
| |
| fn context<C>(self, context: C) -> Self::Output |
| where |
| C: IntoCowStr, |
| { |
| match self { |
| Ok(val) => Ok(val), |
| Err(err) => Err(err.context(context)), |
| } |
| } |
| |
| fn with_context<C, F>(self, f: F) -> Self::Output |
| where |
| C: IntoCowStr, |
| F: FnOnce() -> C, |
| { |
| match self { |
| Ok(val) => Ok(val), |
| Err(err) => Err(err.with_context(f)), |
| } |
| } |
| } |
| |
| impl ErrorExt for io::Error { |
| type Output = Error; |
| |
| fn context<C>(self, context: C) -> Self::Output |
| where |
| C: IntoCowStr, |
| { |
| Error::from(self).context(context) |
| } |
| |
| fn with_context<C, F>(self, f: F) -> Self::Output |
| where |
| C: IntoCowStr, |
| F: FnOnce() -> C, |
| { |
| Error::from(self).with_context(f) |
| } |
| } |
| |
| /// A trait providing conversion shortcuts for creating `Error` |
| /// instances. |
| pub trait IntoError<T>: private::Sealed |
| where |
| Self: Sized, |
| { |
| fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error> |
| where |
| C: ToString, |
| F: FnOnce() -> C; |
| |
| #[inline] |
| fn ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error> |
| where |
| C: ToString, |
| F: FnOnce() -> C, |
| { |
| self.ok_or_error(io::ErrorKind::InvalidData, f) |
| } |
| } |
| |
| impl<T> IntoError<T> for Option<T> { |
| #[inline] |
| fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error> |
| where |
| C: ToString, |
| F: FnOnce() -> C, |
| { |
| self.ok_or_else(|| Error::with_io_error(kind, f().to_string())) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use std::mem::size_of; |
| |
| /// Check various features of our `Str` wrapper type. |
| #[test] |
| fn str_wrapper() { |
| let b = "test string".to_string().into_boxed_str(); |
| let s: &Str = b.borrow(); |
| let _b: Box<str> = s.to_owned(); |
| |
| assert_eq!(s.to_string(), b.deref()); |
| assert_eq!(format!("{s:?}"), "Str(\"test string\")"); |
| } |
| |
| /// Check that our `Error` type's size is as expected. |
| #[test] |
| fn error_size() { |
| assert_eq!(size_of::<Error>(), size_of::<usize>()); |
| assert_eq!(size_of::<ErrorImpl>(), 4 * size_of::<usize>()); |
| } |
| |
| /// Check that we can format errors as expected. |
| #[test] |
| fn error_formatting() { |
| let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data"); |
| let err = Error::from(err); |
| |
| let src = err.source(); |
| assert!(src.is_none(), "{src:?}"); |
| assert!(err.error.is_owned().is_none()); |
| assert_eq!(err.kind(), ErrorKind::InvalidData); |
| assert_eq!(format!("{err}"), "some invalid data"); |
| assert_eq!(format!("{err:#}"), "some invalid data"); |
| assert_eq!(format!("{err:?}"), "Error: some invalid data"); |
| // TODO: The inner format may not actually be all that stable. |
| let expected = r#"Io( |
| Custom { |
| kind: InvalidData, |
| error: "some invalid data", |
| }, |
| )"#; |
| assert_eq!(format!("{err:#?}"), expected); |
| |
| let err = err.context("inner context"); |
| let src = err.source(); |
| assert!(src.is_some(), "{src:?}"); |
| assert!(!err.error.is_owned().unwrap()); |
| assert_eq!(err.kind(), ErrorKind::InvalidData); |
| assert_eq!(format!("{err}"), "inner context"); |
| assert_eq!(format!("{err:#}"), "inner context: some invalid data"); |
| |
| let expected = r#"Error: inner context |
| |
| Caused by: |
| some invalid data"#; |
| assert_eq!(format!("{err:?}"), expected); |
| // Nope, not going to bother. |
| assert_ne!(format!("{err:#?}"), ""); |
| |
| let err = err.context("outer context".to_string()); |
| let src = err.source(); |
| assert!(src.is_some(), "{src:?}"); |
| assert!(err.error.is_owned().unwrap()); |
| assert_eq!(err.kind(), ErrorKind::InvalidData); |
| assert_eq!(format!("{err}"), "outer context"); |
| assert_eq!( |
| format!("{err:#}"), |
| "outer context: inner context: some invalid data" |
| ); |
| |
| let expected = r#"Error: outer context |
| |
| Caused by: |
| inner context |
| some invalid data"#; |
| assert_eq!(format!("{err:?}"), expected); |
| assert_ne!(format!("{err:#?}"), ""); |
| } |
| } |