1 // Take a look at the license at the top of the repository in the LICENSE file. 2 3 //! `Error` binding and helper trait. 4 5 use crate::translate::*; 6 use crate::Quark; 7 use std::borrow::Cow; 8 use std::error; 9 use std::ffi::CStr; 10 use std::fmt; 11 use std::mem; 12 use std::str; 13 14 wrapper! { 15 /// A generic error capable of representing various error domains (types). 16 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] 17 #[doc(alias = "GError")] 18 pub struct Error(Boxed<ffi::GError>); 19 20 match fn { 21 copy => |ptr| ffi::g_error_copy(ptr), 22 free => |ptr| ffi::g_error_free(ptr), 23 type_ => || ffi::g_error_get_type(), 24 } 25 } 26 27 unsafe impl Send for Error {} 28 unsafe impl Sync for Error {} 29 30 impl Error { 31 /// Creates an error with supplied error enum variant and message. 32 #[doc(alias = "g_error_new_literal")] new<T: ErrorDomain>(error: T, message: &str) -> Error33 pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error { 34 unsafe { 35 from_glib_full(ffi::g_error_new_literal( 36 T::domain().into_glib(), 37 error.code(), 38 message.to_glib_none().0, 39 )) 40 } 41 } 42 43 /// Checks if the error domain matches `T`. is<T: ErrorDomain>(&self) -> bool44 pub fn is<T: ErrorDomain>(&self) -> bool { 45 self.0.domain == T::domain().into_glib() 46 } 47 48 /// Tries to convert to a specific error enum. 49 /// 50 /// Returns `Some` if the error belongs to the enum's error domain and 51 /// `None` otherwise. 52 /// 53 /// # Examples 54 /// 55 /// ```ignore 56 /// if let Some(file_error) = error.kind::<FileError>() { 57 /// match file_error { 58 /// FileError::Exist => ... 59 /// FileError::Isdir => ... 60 /// ... 61 /// } 62 /// } 63 /// ``` kind<T: ErrorDomain>(&self) -> Option<T>64 pub fn kind<T: ErrorDomain>(&self) -> Option<T> { 65 if self.is::<T>() { 66 T::from(self.0.code) 67 } else { 68 None 69 } 70 } 71 message(&self) -> &str72 fn message(&self) -> &str { 73 unsafe { 74 let bytes = CStr::from_ptr(self.0.message).to_bytes(); 75 str::from_utf8(bytes) 76 .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap()) 77 } 78 } 79 80 /// Consumes the `Error` and returns the corresponding `GError` pointer. into_raw(self) -> *mut ffi::GError81 pub fn into_raw(self) -> *mut ffi::GError { 82 let mut e = mem::ManuallyDrop::new(self); 83 e.to_glib_none_mut().0 84 } 85 } 86 87 impl fmt::Display for Error { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 89 f.write_str(self.message()) 90 } 91 } 92 93 impl fmt::Debug for Error { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 95 f.debug_struct("Error") 96 .field("domain", unsafe { &crate::Quark::from_glib(self.0.domain) }) 97 .field("code", &self.0.code) 98 .field("message", &self.message()) 99 .finish() 100 } 101 } 102 103 impl error::Error for Error {} 104 105 /// `GLib` error domain. 106 /// 107 /// This trait is implemented by error enums that represent error domains (types). 108 pub trait ErrorDomain: Copy { 109 /// Returns the quark identifying the error domain. 110 /// 111 /// As returned from `g_some_error_quark`. domain() -> Quark112 fn domain() -> Quark; 113 114 /// Gets the integer representation of the variant. code(self) -> i32115 fn code(self) -> i32; 116 117 /// Tries to convert an integer code to an enum variant. 118 /// 119 /// By convention, the `Failed` variant, if present, is a catch-all, 120 /// i.e. any unrecognized codes map to it. from(code: i32) -> Option<Self> where Self: Sized121 fn from(code: i32) -> Option<Self> 122 where 123 Self: Sized; 124 } 125 126 /// Generic error used for functions that fail without any further information 127 #[macro_export] 128 macro_rules! bool_error( 129 // Plain strings 130 ($msg:expr) => { 131 $crate::BoolError::new($msg, file!(), module_path!(), line!()) 132 }; 133 134 // Format strings 135 ($($msg:tt)*) => { { 136 $crate::BoolError::new(format!($($msg)*), file!(), module_path!(), line!()) 137 }}; 138 ); 139 140 #[macro_export] 141 macro_rules! result_from_gboolean( 142 // Plain strings 143 ($ffi_bool:expr, $msg:expr) => { 144 $crate::BoolError::from_glib($ffi_bool, $msg, file!(), module_path!(), line!()) 145 }; 146 147 // Format strings 148 ($ffi_bool:expr, $($msg:tt)*) => { { 149 $crate::BoolError::from_glib( 150 $ffi_bool, 151 format!($($msg)*), 152 file!(), 153 module_path!(), 154 line!(), 155 ) 156 }}; 157 ); 158 159 #[derive(Debug, Clone)] 160 pub struct BoolError { 161 pub message: Cow<'static, str>, 162 #[doc(hidden)] 163 pub filename: &'static str, 164 #[doc(hidden)] 165 pub function: &'static str, 166 #[doc(hidden)] 167 pub line: u32, 168 } 169 170 impl BoolError { new<Msg: Into<Cow<'static, str>>>( message: Msg, filename: &'static str, function: &'static str, line: u32, ) -> Self171 pub fn new<Msg: Into<Cow<'static, str>>>( 172 message: Msg, 173 filename: &'static str, 174 function: &'static str, 175 line: u32, 176 ) -> Self { 177 Self { 178 message: message.into(), 179 filename, 180 function, 181 line, 182 } 183 } 184 from_glib<Msg: Into<Cow<'static, str>>>( b: ffi::gboolean, message: Msg, filename: &'static str, function: &'static str, line: u32, ) -> Result<(), Self>185 pub fn from_glib<Msg: Into<Cow<'static, str>>>( 186 b: ffi::gboolean, 187 message: Msg, 188 filename: &'static str, 189 function: &'static str, 190 line: u32, 191 ) -> Result<(), Self> { 192 match b { 193 ffi::GFALSE => Err(BoolError::new(message, filename, function, line)), 194 _ => Ok(()), 195 } 196 } 197 } 198 199 impl fmt::Display for BoolError { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 201 f.write_str(&self.message) 202 } 203 } 204 205 impl error::Error for BoolError {} 206 207 #[cfg(test)] 208 mod tests { 209 use super::*; 210 use std::ffi::CString; 211 212 #[test] test_error_kind()213 fn test_error_kind() { 214 let e = Error::new(crate::FileError::Failed, "Failed"); 215 assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed)); 216 assert_eq!(e.kind::<crate::KeyFileError>(), None); 217 } 218 219 #[test] test_into_raw()220 fn test_into_raw() { 221 let e = Error::new(crate::FileError::Failed, "Failed").into_raw(); 222 unsafe { 223 assert_eq!((*e).domain, ffi::g_file_error_quark()); 224 assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED); 225 assert_eq!( 226 CStr::from_ptr((*e).message), 227 CString::new("Failed").unwrap().as_c_str() 228 ); 229 230 ffi::g_error_free(e); 231 } 232 } 233 234 #[test] test_bool_error()235 fn test_bool_error() { 236 let from_static_msg = bool_error!("Static message"); 237 assert_eq!(from_static_msg.to_string(), "Static message"); 238 239 let from_dynamic_msg = bool_error!("{} message", "Dynamic"); 240 assert_eq!(from_dynamic_msg.to_string(), "Dynamic message"); 241 242 let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message"); 243 assert!(false_static_res.is_err()); 244 let static_err = false_static_res.err().unwrap(); 245 assert_eq!(static_err.to_string(), "Static message"); 246 247 let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message"); 248 assert!(true_static_res.is_ok()); 249 250 let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic"); 251 assert!(false_dynamic_res.is_err()); 252 let dynamic_err = false_dynamic_res.err().unwrap(); 253 assert_eq!(dynamic_err.to_string(), "Dynamic message"); 254 255 let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic"); 256 assert!(true_dynamic_res.is_ok()); 257 } 258 } 259