diff --git a/rust/util/src/error.rs b/rust/util/src/error.rs index 2a57c7fd5f..11b574ca59 100644 --- a/rust/util/src/error.rs +++ b/rust/util/src/error.rs @@ -38,6 +38,7 @@ use std::{ borrow::Cow, ffi::{c_char, c_int, c_void, CStr}, fmt::{self, Display}, + ops::Deref, panic, ptr, }; @@ -49,118 +50,85 @@ pub type Result = std::result::Result; #[derive(Debug)] pub struct Error { - msg: Option>, - /// Appends the print string of the error to the msg if not None - cause: Option, + cause: anyhow::Error, file: &'static str, line: u32, } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.cause.as_ref().map(AsRef::as_ref) - } +impl Deref for Error { + type Target = anyhow::Error; - #[allow(deprecated)] - fn description(&self) -> &str { - self.msg - .as_deref() - .or_else(|| self.cause.as_deref().map(std::error::Error::description)) - .expect("no message nor cause?") + fn deref(&self) -> &Self::Target { + &self.cause } } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut prefix = ""; - if let Some(ref msg) = self.msg { - write!(f, "{msg}")?; - prefix = ": "; - } - if let Some(ref cause) = self.cause { - write!(f, "{prefix}{cause}")?; - } else if prefix.is_empty() { - panic!("no message nor cause?"); - } - Ok(()) + Display::fmt(&format_args!("{:#}", self.cause), f) } } -impl From> for Error { +impl From for Error +where + anyhow::Error: From, +{ #[track_caller] - fn from(msg: Cow<'static, str>) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(msg), - cause: None, - file: location.file(), - line: location.line(), - } - } -} - -impl From for Error { - #[track_caller] - fn from(msg: String) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(Cow::Owned(msg)), - cause: None, - file: location.file(), - line: location.line(), - } - } -} - -impl From<&'static str> for Error { - #[track_caller] - fn from(msg: &'static str) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(Cow::Borrowed(msg)), - cause: None, - file: location.file(), - line: location.line(), - } - } -} - -impl From for Error { - #[track_caller] - fn from(error: anyhow::Error) -> Self { - let location = panic::Location::caller(); - Error { - msg: None, - cause: Some(error), - file: location.file(), - line: location.line(), - } + fn from(src: E) -> Self { + Self::new(anyhow::Error::from(src)) } } impl Error { + /// Create a new error from an [`anyhow::Error`]. + /// + /// This wraps the error with QEMU's location tracking information. + /// Most code should use the `?` operator instead of calling this directly. + #[track_caller] + pub fn new(cause: anyhow::Error) -> Self { + let location = panic::Location::caller(); + Self { + cause, + file: location.file(), + line: location.line(), + } + } + + /// Create a new error from a string message. + /// + /// This is a convenience wrapper around [`Error::new`] for simple string + /// errors. Most code should use the [`ensure!`](crate::ensure) macro + /// instead of calling this directly. + #[track_caller] + pub fn msg(src: impl Into>) -> Self { + Self::new(anyhow::Error::msg(src.into())) + } + #[track_caller] #[doc(hidden)] + #[inline(always)] pub fn format(args: fmt::Arguments) -> Self { - if let Some(msg) = args.as_str() { - Self::from(msg) - } else { - let msg = fmt::format(args); - Self::from(msg) - } + // anyhow::Error::msg will allocate anyway, might as well let fmt::format doit. + let msg = fmt::format(args); + Self::new(anyhow::Error::msg(msg)) } /// Create a new error, prepending `msg` to the /// description of `cause` #[track_caller] pub fn with_error(msg: impl Into>, cause: impl Into) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(msg.into()), - cause: Some(cause.into()), - file: location.file(), - line: location.line(), + fn do_with_error( + msg: Cow<'static, str>, + cause: anyhow::Error, + location: &'static panic::Location<'static>, + ) -> Error { + Error { + cause: cause.context(msg), + file: location.file(), + line: location.line(), + } } + do_with_error(msg.into(), cause.into(), panic::Location::caller()) } /// Consume a result, returning `false` if it is an error and @@ -326,8 +294,7 @@ impl FromForeign for Error { }; Error { - msg: FromForeign::cloned_from_foreign(error.msg), - cause: None, + cause: anyhow::Error::msg(String::cloned_from_foreign(error.msg)), file: file.unwrap(), line: error.line as u32, } @@ -376,8 +343,8 @@ macro_rules! ensure { }; ($cond:expr, $err:expr $(,)?) => { if !$cond { - let s = ::std::borrow::Cow::<'static, str>::from($err); - return $crate::Result::Err(s.into()); + let e = $crate::Error::msg($err); + return $crate::Result::Err(e); } }; } @@ -416,19 +383,10 @@ mod tests { unsafe { CStr::from_ptr(bindings::error_get_pretty(local_err)) } } - #[test] - #[allow(deprecated)] - fn test_description() { - use std::error::Error; - - assert_eq!(super::Error::from("msg").description(), "msg"); - assert_eq!(super::Error::from("msg".to_owned()).description(), "msg"); - } - #[test] fn test_display() { - assert_eq!(&*format!("{}", Error::from("msg")), "msg"); - assert_eq!(&*format!("{}", Error::from("msg".to_owned())), "msg"); + assert_eq!(&*format!("{}", Error::msg("msg")), "msg"); + assert_eq!(&*format!("{}", Error::msg("msg".to_owned())), "msg"); assert_eq!(&*format!("{}", Error::from(anyhow!("msg"))), "msg"); assert_eq!( @@ -445,7 +403,7 @@ mod tests { assert!(Error::bool_or_propagate(Ok(()), &mut local_err)); assert_eq!(local_err, ptr::null_mut()); - let my_err = Error::from("msg"); + let my_err = Error::msg("msg"); assert!(!Error::bool_or_propagate(Err(my_err), &mut local_err)); assert_ne!(local_err, ptr::null_mut()); assert_eq!(error_get_pretty(local_err), c"msg"); @@ -462,7 +420,7 @@ mod tests { assert_eq!(String::from_foreign(ret), "abc"); assert_eq!(local_err, ptr::null_mut()); - let my_err = Error::from("msg"); + let my_err = Error::msg("msg"); assert_eq!( Error::ptr_or_propagate(Err::(my_err), &mut local_err), ptr::null_mut()