// Take a look at the license at the top of the repository in the LICENSE file. // rustdoc-stripper-ignore-next //! `Error` binding and helper trait. use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str}; use crate::{translate::*, Quark}; wrapper! { // rustdoc-stripper-ignore-next /// A generic error capable of representing various error domains (types). #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] #[doc(alias = "GError")] pub struct Error(Boxed); match fn { copy => |ptr| ffi::g_error_copy(ptr), free => |ptr| ffi::g_error_free(ptr), type_ => || ffi::g_error_get_type(), } } unsafe impl Send for Error {} unsafe impl Sync for Error {} impl Error { // rustdoc-stripper-ignore-next /// Creates an error with supplied error enum variant and message. #[doc(alias = "g_error_new_literal")] #[doc(alias = "g_error_new")] pub fn new(error: T, message: &str) -> Error { unsafe { from_glib_full(ffi::g_error_new_literal( T::domain().into_glib(), error.code(), message.to_glib_none().0, )) } } // rustdoc-stripper-ignore-next /// Checks if the error domain matches `T`. pub fn is(&self) -> bool { self.inner.domain == T::domain().into_glib() } // rustdoc-stripper-ignore-next /// Returns the error domain quark pub fn domain(&self) -> Quark { unsafe { from_glib(self.inner.domain) } } // rustdoc-stripper-ignore-next /// Checks if the error matches the specified domain and error code. #[doc(alias = "g_error_matches")] pub fn matches(&self, err: T) -> bool { self.is::() && self.inner.code == err.code() } // rustdoc-stripper-ignore-next /// Tries to convert to a specific error enum. /// /// Returns `Some` if the error belongs to the enum's error domain and /// `None` otherwise. /// /// # Examples /// /// ```ignore /// if let Some(file_error) = error.kind::() { /// match file_error { /// FileError::Exist => ... /// FileError::Isdir => ... /// ... /// } /// } /// ``` pub fn kind(&self) -> Option { if self.is::() { T::from(self.inner.code) } else { None } } // rustdoc-stripper-ignore-next /// Returns the error message /// /// Most of the time you can simply print the error since it implements the `Display` /// trait, but you can use this method if you need to have the message as a `&str`. pub fn message(&self) -> &str { unsafe { let bytes = CStr::from_ptr(self.inner.message).to_bytes(); str::from_utf8(bytes) .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap()) } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.message()) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Error") .field("domain", unsafe { &crate::Quark::from_glib(self.inner.domain) }) .field("code", &self.inner.code) .field("message", &self.message()) .finish() } } impl error::Error for Error {} impl From for Error { fn from(e: Infallible) -> Self { match e {} } } // rustdoc-stripper-ignore-next /// `GLib` error domain. /// /// This trait is implemented by error enums that represent error domains (types). pub trait ErrorDomain: Copy { // rustdoc-stripper-ignore-next /// Returns the quark identifying the error domain. /// /// As returned from `g_some_error_quark`. fn domain() -> Quark; // rustdoc-stripper-ignore-next /// Gets the integer representation of the variant. fn code(self) -> i32; // rustdoc-stripper-ignore-next /// Tries to convert an integer code to an enum variant. /// /// By convention, the `Failed` variant, if present, is a catch-all, /// i.e. any unrecognized codes map to it. fn from(code: i32) -> Option where Self: Sized; } // rustdoc-stripper-ignore-next /// Generic error used for functions that fail without any further information #[macro_export] macro_rules! bool_error( // Plain strings ($msg:expr) => {{ $crate::BoolError::new( $msg, file!(), $crate::function_name!(), line!(), ) }}; // Format strings ($($msg:tt)*) => {{ $crate::BoolError::new( format!($($msg)*), file!(), $crate::function_name!(), line!(), ) }}; ); #[macro_export] macro_rules! result_from_gboolean( // Plain strings ($ffi_bool:expr, $msg:expr) => {{ $crate::BoolError::from_glib( $ffi_bool, $msg, file!(), $crate::function_name!(), line!(), ) }}; // Format strings ($ffi_bool:expr, $($msg:tt)*) => {{ $crate::BoolError::from_glib( $ffi_bool, format!($($msg)*), file!(), $crate::function_name!(), line!(), ) }}; ); #[derive(Debug, Clone)] pub struct BoolError { pub message: Cow<'static, str>, #[doc(hidden)] pub filename: &'static str, #[doc(hidden)] pub function: &'static str, #[doc(hidden)] pub line: u32, } impl BoolError { pub fn new( message: impl Into>, filename: &'static str, function: &'static str, line: u32, ) -> Self { Self { message: message.into(), filename, function, line, } } pub fn from_glib( b: ffi::gboolean, message: impl Into>, filename: &'static str, function: &'static str, line: u32, ) -> Result<(), Self> { match b { ffi::GFALSE => Err(BoolError::new(message, filename, function, line)), _ => Ok(()), } } } impl fmt::Display for BoolError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.message) } } impl error::Error for BoolError {} #[cfg(test)] mod tests { use std::ffi::CString; use super::*; use crate::prelude::*; #[test] fn test_error_matches() { let e = Error::new(crate::FileError::Failed, "Failed"); assert!(e.matches(crate::FileError::Failed)); assert!(!e.matches(crate::FileError::Again)); assert!(!e.matches(crate::KeyFileError::NotFound)); } #[test] fn test_error_kind() { let e = Error::new(crate::FileError::Failed, "Failed"); assert_eq!(e.kind::(), Some(crate::FileError::Failed)); assert_eq!(e.kind::(), None); } #[test] fn test_into_raw() { unsafe { let e: *mut ffi::GError = Error::new(crate::FileError::Failed, "Failed").into_glib_ptr(); assert_eq!((*e).domain, ffi::g_file_error_quark()); assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED); assert_eq!( CStr::from_ptr((*e).message), CString::new("Failed").unwrap().as_c_str() ); ffi::g_error_free(e); } } #[test] fn test_bool_error() { let from_static_msg = bool_error!("Static message"); assert_eq!(from_static_msg.to_string(), "Static message"); let from_dynamic_msg = bool_error!("{} message", "Dynamic"); assert_eq!(from_dynamic_msg.to_string(), "Dynamic message"); let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message"); assert!(false_static_res.is_err()); let static_err = false_static_res.err().unwrap(); assert_eq!(static_err.to_string(), "Static message"); let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message"); assert!(true_static_res.is_ok()); let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic"); assert!(false_dynamic_res.is_err()); let dynamic_err = false_dynamic_res.err().unwrap(); assert_eq!(dynamic_err.to_string(), "Dynamic message"); let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic"); assert!(true_dynamic_res.is_ok()); } #[test] fn test_value() { let e1 = Error::new(crate::FileError::Failed, "Failed"); // This creates a copy ... let v = e1.to_value(); // ... so we have to get the raw pointer from inside the value to check for equality. let ptr = unsafe { gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError }; let e2 = v.get::<&Error>().unwrap(); assert_eq!(ptr, e2.to_glib_none().0); } }