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    /// Creates an error with supplied error domain quark, code and message.
44    ///
45    /// This is useful when you need to create an error with the same domain and code
46    /// as an existing error but with a different message.
47    ///
48    /// # Examples
49    ///
50    /// ```ignore
51    /// let original = Error::new(FileError::Failed, "Original message");
52    /// let modified = Error::with_domain(
53    ///     original.domain(),
54    ///     original.code(),
55    ///     "Modified message"
56    /// );
57    /// ```
58    #[doc(alias = "g_error_new_literal")]
59    pub fn with_domain(domain: Quark, code: i32, message: &str) -> Error {
60        unsafe {
61            from_glib_full(ffi::g_error_new_literal(
62                domain.into_glib(),
63                code,
64                message.to_glib_none().0,
65            ))
66        }
67    }
68
69    // rustdoc-stripper-ignore-next
70    /// Checks if the error domain matches `T`.
71    pub fn is<T: ErrorDomain>(&self) -> bool {
72        self.inner.domain == T::domain().into_glib()
73    }
74
75    // rustdoc-stripper-ignore-next
76    /// Returns the error domain quark
77    pub fn domain(&self) -> Quark {
78        unsafe { from_glib(self.inner.domain) }
79    }
80
81    // rustdoc-stripper-ignore-next
82    /// Returns the error code
83    pub fn code(&self) -> i32 {
84        self.inner.code
85    }
86
87    // rustdoc-stripper-ignore-next
88    /// Checks if the error matches the specified domain and error code.
89    #[doc(alias = "g_error_matches")]
90    pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
91        self.is::<T>() && self.inner.code == err.code()
92    }
93
94    // rustdoc-stripper-ignore-next
95    /// Tries to convert to a specific error enum.
96    ///
97    /// Returns `Some` if the error belongs to the enum's error domain and
98    /// `None` otherwise.
99    ///
100    /// # Examples
101    ///
102    /// ```ignore
103    /// if let Some(file_error) = error.kind::<FileError>() {
104    ///     match file_error {
105    ///         FileError::Exist => ...
106    ///         FileError::Isdir => ...
107    ///         ...
108    ///     }
109    /// }
110    /// ```
111    pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
112        if self.is::<T>() {
113            T::from(self.inner.code)
114        } else {
115            None
116        }
117    }
118
119    // rustdoc-stripper-ignore-next
120    /// Returns the error message
121    ///
122    /// Most of the time you can simply print the error since it implements the `Display`
123    /// trait, but you can use this method if you need to have the message as a `&str`.
124    pub fn message(&self) -> &str {
125        unsafe {
126            let bytes = CStr::from_ptr(self.inner.message).to_bytes();
127            str::from_utf8(bytes)
128                .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
129        }
130    }
131}
132
133impl fmt::Display for Error {
134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135        f.write_str(self.message())
136    }
137}
138
139impl fmt::Debug for Error {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        f.debug_struct("Error")
142            .field("domain", unsafe {
143                &crate::Quark::from_glib(self.inner.domain)
144            })
145            .field("code", &self.inner.code)
146            .field("message", &self.message())
147            .finish()
148    }
149}
150
151impl error::Error for Error {}
152
153impl From<Infallible> for Error {
154    fn from(e: Infallible) -> Self {
155        match e {}
156    }
157}
158
159// rustdoc-stripper-ignore-next
160/// `GLib` error domain.
161///
162/// This trait is implemented by error enums that represent error domains (types).
163pub trait ErrorDomain: Copy {
164    // rustdoc-stripper-ignore-next
165    /// Returns the quark identifying the error domain.
166    ///
167    /// As returned from `g_some_error_quark`.
168    fn domain() -> Quark;
169
170    // rustdoc-stripper-ignore-next
171    /// Gets the integer representation of the variant.
172    fn code(self) -> i32;
173
174    // rustdoc-stripper-ignore-next
175    /// Tries to convert an integer code to an enum variant.
176    ///
177    /// By convention, the `Failed` variant, if present, is a catch-all,
178    /// i.e. any unrecognized codes map to it.
179    fn from(code: i32) -> Option<Self>
180    where
181        Self: Sized;
182}
183
184// rustdoc-stripper-ignore-next
185/// Generic error used for functions that fail without any further information
186#[macro_export]
187macro_rules! bool_error(
188    ($($msg:tt)*) =>  {{
189        match ::std::format_args!($($msg)*) {
190            formatted => {
191                if let Some(s) = formatted.as_str() {
192                    $crate::BoolError::new(
193                        s,
194                        file!(),
195                        $crate::function_name!(),
196                        line!()
197                    )
198                } else {
199                    $crate::BoolError::new(
200                        formatted.to_string(),
201                        file!(),
202                        $crate::function_name!(),
203                        line!(),
204                    )
205                }
206            }
207        }
208    }};
209);
210
211#[macro_export]
212macro_rules! result_from_gboolean(
213    ($ffi_bool:expr, $($msg:tt)*) =>  {{
214        match ::std::format_args!($($msg)*) {
215            formatted => {
216                if let Some(s) = formatted.as_str() {
217                    $crate::BoolError::from_glib(
218                        $ffi_bool,
219                        s,
220                        file!(),
221                        $crate::function_name!(),
222                        line!(),
223                    )
224                } else {
225                    $crate::BoolError::from_glib(
226                        $ffi_bool,
227                        formatted.to_string(),
228                        file!(),
229                        $crate::function_name!(),
230                        line!(),
231                    )
232                }
233            }
234        }
235
236
237    }};
238);
239
240#[derive(Debug, Clone)]
241pub struct BoolError {
242    pub message: Cow<'static, str>,
243    #[doc(hidden)]
244    pub filename: &'static str,
245    #[doc(hidden)]
246    pub function: &'static str,
247    #[doc(hidden)]
248    pub line: u32,
249}
250
251impl BoolError {
252    pub fn new(
253        message: impl Into<Cow<'static, str>>,
254        filename: &'static str,
255        function: &'static str,
256        line: u32,
257    ) -> Self {
258        Self {
259            message: message.into(),
260            filename,
261            function,
262            line,
263        }
264    }
265
266    pub fn from_glib(
267        b: ffi::gboolean,
268        message: impl Into<Cow<'static, str>>,
269        filename: &'static str,
270        function: &'static str,
271        line: u32,
272    ) -> Result<(), Self> {
273        match b {
274            ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
275            _ => Ok(()),
276        }
277    }
278}
279
280impl fmt::Display for BoolError {
281    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282        f.write_str(&self.message)
283    }
284}
285
286impl error::Error for BoolError {}
287
288#[cfg(test)]
289mod tests {
290    use std::ffi::CString;
291
292    use super::*;
293    use crate::prelude::*;
294
295    #[test]
296    fn test_error_matches() {
297        let e = Error::new(crate::FileError::Failed, "Failed");
298        assert!(e.matches(crate::FileError::Failed));
299        assert!(!e.matches(crate::FileError::Again));
300        assert!(!e.matches(crate::KeyFileError::NotFound));
301    }
302
303    #[test]
304    fn test_error_kind() {
305        let e = Error::new(crate::FileError::Failed, "Failed");
306        assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
307        assert_eq!(e.kind::<crate::KeyFileError>(), None);
308    }
309
310    #[test]
311    fn test_into_raw() {
312        unsafe {
313            let e: *mut ffi::GError =
314                Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
315            assert_eq!((*e).domain, ffi::g_file_error_quark());
316            assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
317            assert_eq!(
318                CStr::from_ptr((*e).message),
319                CString::new("Failed").unwrap().as_c_str()
320            );
321
322            ffi::g_error_free(e);
323        }
324    }
325
326    #[test]
327    fn test_bool_error() {
328        let from_static_msg = bool_error!("Static message");
329        assert_eq!(from_static_msg.to_string(), "Static message");
330
331        let from_dynamic_msg = bool_error!("{} message", "Dynamic");
332        assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
333
334        let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
335        assert!(false_static_res.is_err());
336        let static_err = false_static_res.err().unwrap();
337        assert_eq!(static_err.to_string(), "Static message");
338
339        let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
340        assert!(true_static_res.is_ok());
341
342        let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
343        assert!(false_dynamic_res.is_err());
344        let dynamic_err = false_dynamic_res.err().unwrap();
345        assert_eq!(dynamic_err.to_string(), "Dynamic message");
346
347        let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
348        assert!(true_dynamic_res.is_ok());
349    }
350
351    #[test]
352    fn test_value() {
353        let e1 = Error::new(crate::FileError::Failed, "Failed");
354        // This creates a copy ...
355        let v = e1.to_value();
356        // ... so we have to get the raw pointer from inside the value to check for equality.
357        let ptr = unsafe {
358            crate::gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError
359        };
360
361        let e2 = v.get::<&Error>().unwrap();
362
363        assert_eq!(ptr, e2.to_glib_none().0);
364    }
365
366    #[test]
367    fn test_from_quark() {
368        let original = Error::new(crate::FileError::Failed, "Original message");
369        let modified = Error::with_domain(original.domain(), original.code(), "Modified message");
370
371        // Should have same domain and code
372        assert_eq!(original.domain(), modified.domain());
373        assert_eq!(original.code(), modified.code());
374        assert!(modified.matches(crate::FileError::Failed));
375
376        // But different message
377        assert_eq!(modified.message(), "Modified message");
378        assert_ne!(original.message(), modified.message());
379    }
380}