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