glib/
error.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3// rustdoc-stripper-ignore-next
4//! `Error` binding and helper trait.
5
6use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str};
7
8use crate::{ffi, translate::*, Quark};
9
10wrapper! {
11    // rustdoc-stripper-ignore-next
12    /// A generic error capable of representing various error domains (types).
13    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
14    #[doc(alias = "GError")]
15    pub struct Error(Boxed<ffi::GError>);
16
17    match fn {
18        copy => |ptr| ffi::g_error_copy(ptr),
19        free => |ptr| ffi::g_error_free(ptr),
20        type_ => || ffi::g_error_get_type(),
21    }
22}
23
24unsafe impl Send for Error {}
25unsafe impl Sync for Error {}
26
27impl Error {
28    // rustdoc-stripper-ignore-next
29    /// Creates an error with supplied error enum variant and message.
30    #[doc(alias = "g_error_new_literal")]
31    #[doc(alias = "g_error_new")]
32    pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error {
33        unsafe {
34            from_glib_full(ffi::g_error_new_literal(
35                T::domain().into_glib(),
36                error.code(),
37                message.to_glib_none().0,
38            ))
39        }
40    }
41
42    // rustdoc-stripper-ignore-next
43    /// Checks if the error domain matches `T`.
44    pub fn is<T: ErrorDomain>(&self) -> bool {
45        self.inner.domain == T::domain().into_glib()
46    }
47
48    // rustdoc-stripper-ignore-next
49    /// Returns the error domain quark
50    pub fn domain(&self) -> Quark {
51        unsafe { from_glib(self.inner.domain) }
52    }
53
54    // rustdoc-stripper-ignore-next
55    /// Checks if the error matches the specified domain and error code.
56    #[doc(alias = "g_error_matches")]
57    pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
58        self.is::<T>() && self.inner.code == err.code()
59    }
60
61    // rustdoc-stripper-ignore-next
62    /// Tries to convert to a specific error enum.
63    ///
64    /// Returns `Some` if the error belongs to the enum's error domain and
65    /// `None` otherwise.
66    ///
67    /// # Examples
68    ///
69    /// ```ignore
70    /// if let Some(file_error) = error.kind::<FileError>() {
71    ///     match file_error {
72    ///         FileError::Exist => ...
73    ///         FileError::Isdir => ...
74    ///         ...
75    ///     }
76    /// }
77    /// ```
78    pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
79        if self.is::<T>() {
80            T::from(self.inner.code)
81        } else {
82            None
83        }
84    }
85
86    // rustdoc-stripper-ignore-next
87    /// Returns the error message
88    ///
89    /// Most of the time you can simply print the error since it implements the `Display`
90    /// trait, but you can use this method if you need to have the message as a `&str`.
91    pub fn message(&self) -> &str {
92        unsafe {
93            let bytes = CStr::from_ptr(self.inner.message).to_bytes();
94            str::from_utf8(bytes)
95                .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
96        }
97    }
98}
99
100impl fmt::Display for Error {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        f.write_str(self.message())
103    }
104}
105
106impl fmt::Debug for Error {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        f.debug_struct("Error")
109            .field("domain", unsafe {
110                &crate::Quark::from_glib(self.inner.domain)
111            })
112            .field("code", &self.inner.code)
113            .field("message", &self.message())
114            .finish()
115    }
116}
117
118impl error::Error for Error {}
119
120impl From<Infallible> for Error {
121    fn from(e: Infallible) -> Self {
122        match e {}
123    }
124}
125
126// rustdoc-stripper-ignore-next
127/// `GLib` error domain.
128///
129/// This trait is implemented by error enums that represent error domains (types).
130pub trait ErrorDomain: Copy {
131    // rustdoc-stripper-ignore-next
132    /// Returns the quark identifying the error domain.
133    ///
134    /// As returned from `g_some_error_quark`.
135    fn domain() -> Quark;
136
137    // rustdoc-stripper-ignore-next
138    /// Gets the integer representation of the variant.
139    fn code(self) -> i32;
140
141    // rustdoc-stripper-ignore-next
142    /// Tries to convert an integer code to an enum variant.
143    ///
144    /// By convention, the `Failed` variant, if present, is a catch-all,
145    /// i.e. any unrecognized codes map to it.
146    fn from(code: i32) -> Option<Self>
147    where
148        Self: Sized;
149}
150
151// rustdoc-stripper-ignore-next
152/// Generic error used for functions that fail without any further information
153#[macro_export]
154macro_rules! bool_error(
155    ($($msg:tt)*) =>  {{
156        match ::std::format_args!($($msg)*) {
157            formatted => {
158                if let Some(s) = formatted.as_str() {
159                    $crate::BoolError::new(
160                        s,
161                        file!(),
162                        $crate::function_name!(),
163                        line!()
164                    )
165                } else {
166                    $crate::BoolError::new(
167                        formatted.to_string(),
168                        file!(),
169                        $crate::function_name!(),
170                        line!(),
171                    )
172                }
173            }
174        }
175    }};
176);
177
178#[macro_export]
179macro_rules! result_from_gboolean(
180    ($ffi_bool:expr, $($msg:tt)*) =>  {{
181        match ::std::format_args!($($msg)*) {
182            formatted => {
183                if let Some(s) = formatted.as_str() {
184                    $crate::BoolError::from_glib(
185                        $ffi_bool,
186                        s,
187                        file!(),
188                        $crate::function_name!(),
189                        line!(),
190                    )
191                } else {
192                    $crate::BoolError::from_glib(
193                        $ffi_bool,
194                        formatted.to_string(),
195                        file!(),
196                        $crate::function_name!(),
197                        line!(),
198                    )
199                }
200            }
201        }
202
203
204    }};
205);
206
207#[derive(Debug, Clone)]
208pub struct BoolError {
209    pub message: Cow<'static, str>,
210    #[doc(hidden)]
211    pub filename: &'static str,
212    #[doc(hidden)]
213    pub function: &'static str,
214    #[doc(hidden)]
215    pub line: u32,
216}
217
218impl BoolError {
219    pub fn new(
220        message: impl Into<Cow<'static, str>>,
221        filename: &'static str,
222        function: &'static str,
223        line: u32,
224    ) -> Self {
225        Self {
226            message: message.into(),
227            filename,
228            function,
229            line,
230        }
231    }
232
233    pub fn from_glib(
234        b: ffi::gboolean,
235        message: impl Into<Cow<'static, str>>,
236        filename: &'static str,
237        function: &'static str,
238        line: u32,
239    ) -> Result<(), Self> {
240        match b {
241            ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
242            _ => Ok(()),
243        }
244    }
245}
246
247impl fmt::Display for BoolError {
248    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249        f.write_str(&self.message)
250    }
251}
252
253impl error::Error for BoolError {}
254
255#[cfg(test)]
256mod tests {
257    use std::ffi::CString;
258
259    use super::*;
260    use crate::prelude::*;
261
262    #[test]
263    fn test_error_matches() {
264        let e = Error::new(crate::FileError::Failed, "Failed");
265        assert!(e.matches(crate::FileError::Failed));
266        assert!(!e.matches(crate::FileError::Again));
267        assert!(!e.matches(crate::KeyFileError::NotFound));
268    }
269
270    #[test]
271    fn test_error_kind() {
272        let e = Error::new(crate::FileError::Failed, "Failed");
273        assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
274        assert_eq!(e.kind::<crate::KeyFileError>(), None);
275    }
276
277    #[test]
278    fn test_into_raw() {
279        unsafe {
280            let e: *mut ffi::GError =
281                Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
282            assert_eq!((*e).domain, ffi::g_file_error_quark());
283            assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
284            assert_eq!(
285                CStr::from_ptr((*e).message),
286                CString::new("Failed").unwrap().as_c_str()
287            );
288
289            ffi::g_error_free(e);
290        }
291    }
292
293    #[test]
294    fn test_bool_error() {
295        let from_static_msg = bool_error!("Static message");
296        assert_eq!(from_static_msg.to_string(), "Static message");
297
298        let from_dynamic_msg = bool_error!("{} message", "Dynamic");
299        assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
300
301        let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
302        assert!(false_static_res.is_err());
303        let static_err = false_static_res.err().unwrap();
304        assert_eq!(static_err.to_string(), "Static message");
305
306        let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
307        assert!(true_static_res.is_ok());
308
309        let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
310        assert!(false_dynamic_res.is_err());
311        let dynamic_err = false_dynamic_res.err().unwrap();
312        assert_eq!(dynamic_err.to_string(), "Dynamic message");
313
314        let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
315        assert!(true_dynamic_res.is_ok());
316    }
317
318    #[test]
319    fn test_value() {
320        let e1 = Error::new(crate::FileError::Failed, "Failed");
321        // This creates a copy ...
322        let v = e1.to_value();
323        // ... so we have to get the raw pointer from inside the value to check for equality.
324        let ptr = unsafe {
325            crate::gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError
326        };
327
328        let e2 = v.get::<&Error>().unwrap();
329
330        assert_eq!(ptr, e2.to_glib_none().0);
331    }
332}
OSZAR »