Skip to main content

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::{Quark, ffi, translate::*};
9
10wrapper! {
11    // rustdoc-stripper-ignore-next
12    /// A generic error capable of representing various error domains (types).
13    // rustdoc-stripper-ignore-next-stop
14    /// The `GError` structure contains information about
15    /// an error that has occurred.
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
27unsafe impl Send for Error {}
28unsafe impl Sync for Error {}
29
30impl Error {
31    // rustdoc-stripper-ignore-next
32    /// Creates an error with supplied error enum variant and message.
33    // rustdoc-stripper-ignore-next-stop
34    /// Creates a new #GError with the given @domain and @code,
35    /// and a message formatted with @format.
36    /// ## `domain`
37    /// error domain
38    /// ## `code`
39    /// error code
40    /// ## `format`
41    /// printf()-style format for error message
42    ///
43    /// # Returns
44    ///
45    /// a new #GError
46    #[doc(alias = "g_error_new_literal")]
47    #[doc(alias = "g_error_new")]
48    pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error {
49        unsafe {
50            from_glib_full(ffi::g_error_new_literal(
51                T::domain().into_glib(),
52                error.code(),
53                message.to_glib_none().0,
54            ))
55        }
56    }
57
58    // rustdoc-stripper-ignore-next
59    /// Creates an error with supplied error domain quark, code and message.
60    ///
61    /// This is useful when you need to create an error with the same domain and code
62    /// as an existing error but with a different message.
63    ///
64    /// # Examples
65    ///
66    /// ```ignore
67    /// let original = Error::new(FileError::Failed, "Original message");
68    /// let modified = Error::with_domain(
69    ///     original.domain(),
70    ///     original.code(),
71    ///     "Modified message"
72    /// );
73    /// ```
74    #[doc(alias = "g_error_new_literal")]
75    pub fn with_domain(domain: Quark, code: i32, message: &str) -> Error {
76        unsafe {
77            from_glib_full(ffi::g_error_new_literal(
78                domain.into_glib(),
79                code,
80                message.to_glib_none().0,
81            ))
82        }
83    }
84
85    // rustdoc-stripper-ignore-next
86    /// Checks if the error domain matches `T`.
87    pub fn is<T: ErrorDomain>(&self) -> bool {
88        self.inner.domain == T::domain().into_glib()
89    }
90
91    // rustdoc-stripper-ignore-next
92    /// Returns the error domain quark
93    pub fn domain(&self) -> Quark {
94        unsafe { from_glib(self.inner.domain) }
95    }
96
97    // rustdoc-stripper-ignore-next
98    /// Returns the error code
99    pub fn code(&self) -> i32 {
100        self.inner.code
101    }
102
103    // rustdoc-stripper-ignore-next
104    /// Checks if the error matches the specified domain and error code.
105    // rustdoc-stripper-ignore-next-stop
106    /// Returns [`true`] if @self matches @domain and @code, [`false`]
107    /// otherwise. In particular, when @self is [`None`], [`false`] will
108    /// be returned.
109    ///
110    /// If @domain contains a `FAILED` (or otherwise generic) error code,
111    /// you should generally not check for it explicitly, but should
112    /// instead treat any not-explicitly-recognized error code as being
113    /// equivalent to the `FAILED` code. This way, if the domain is
114    /// extended in the future to provide a more specific error code for
115    /// a certain case, your code will still work.
116    /// ## `domain`
117    /// an error domain
118    /// ## `code`
119    /// an error code
120    ///
121    /// # Returns
122    ///
123    /// whether @self has @domain and @code
124    #[doc(alias = "g_error_matches")]
125    pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
126        self.is::<T>() && self.inner.code == err.code()
127    }
128
129    // rustdoc-stripper-ignore-next
130    /// Tries to convert to a specific error enum.
131    ///
132    /// Returns `Some` if the error belongs to the enum's error domain and
133    /// `None` otherwise.
134    ///
135    /// # Examples
136    ///
137    /// ```ignore
138    /// if let Some(file_error) = error.kind::<FileError>() {
139    ///     match file_error {
140    ///         FileError::Exist => ...
141    ///         FileError::Isdir => ...
142    ///         ...
143    ///     }
144    /// }
145    /// ```
146    pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
147        if self.is::<T>() {
148            T::from(self.inner.code)
149        } else {
150            None
151        }
152    }
153
154    // rustdoc-stripper-ignore-next
155    /// Returns the error message
156    ///
157    /// Most of the time you can simply print the error since it implements the `Display`
158    /// trait, but you can use this method if you need to have the message as a `&str`.
159    pub fn message(&self) -> &str {
160        unsafe {
161            let bytes = CStr::from_ptr(self.inner.message).to_bytes();
162            str::from_utf8(bytes)
163                .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
164        }
165    }
166
167    // rustdoc-stripper-ignore-next
168    /// Prefix the error message with a given string, returning a new [`Error`].
169    #[doc(alias = "g_prefix_error_literal")]
170    #[doc(alias = "prefix_error_literal")]
171    #[doc(alias = "g_prefix_error")]
172    #[doc(alias = "prefix_error")]
173    pub fn prefix(self, name: impl IntoGStr) -> Self {
174        let mut this = self.into_glib_ptr();
175        name.run_with_gstr(|name| unsafe {
176            match () {
177                #[cfg(feature = "v2_70")]
178                () => ffi::g_prefix_error_literal(&mut this, name.as_ptr()),
179                #[cfg(not(feature = "v2_70"))]
180                () => ffi::g_prefix_error(&mut this, c"%s".as_ptr(), name.as_ptr()),
181            }
182            from_glib_full(this)
183        })
184    }
185}
186
187impl fmt::Display for Error {
188    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189        f.write_str(self.message())
190    }
191}
192
193impl fmt::Debug for Error {
194    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195        f.debug_struct("Error")
196            .field("domain", &unsafe {
197                crate::Quark::from_glib(self.inner.domain)
198            })
199            .field("code", &self.inner.code)
200            .field("message", &self.message())
201            .finish()
202    }
203}
204
205impl error::Error for Error {}
206
207impl From<Infallible> for Error {
208    fn from(e: Infallible) -> Self {
209        match e {}
210    }
211}
212
213// rustdoc-stripper-ignore-next
214/// `GLib` error domain.
215///
216/// This trait is implemented by error enums that represent error domains (types).
217pub trait ErrorDomain: Copy {
218    // rustdoc-stripper-ignore-next
219    /// Returns the quark identifying the error domain.
220    ///
221    /// As returned from `g_some_error_quark`.
222    fn domain() -> Quark;
223
224    // rustdoc-stripper-ignore-next
225    /// Gets the integer representation of the variant.
226    fn code(self) -> i32;
227
228    // rustdoc-stripper-ignore-next
229    /// Tries to convert an integer code to an enum variant.
230    ///
231    /// By convention, the `Failed` variant, if present, is a catch-all,
232    /// i.e. any unrecognized codes map to it.
233    fn from(code: i32) -> Option<Self>
234    where
235        Self: Sized;
236}
237
238// rustdoc-stripper-ignore-next
239/// Generic error used for functions that fail without any further information
240#[macro_export]
241macro_rules! bool_error(
242    ($($msg:tt)*) =>  {{
243        match ::std::format_args!($($msg)*) {
244            formatted => {
245                if let Some(s) = formatted.as_str() {
246                    $crate::BoolError::new(
247                        s,
248                        file!(),
249                        $crate::function_name!(),
250                        line!()
251                    )
252                } else {
253                    $crate::BoolError::new(
254                        formatted.to_string(),
255                        file!(),
256                        $crate::function_name!(),
257                        line!(),
258                    )
259                }
260            }
261        }
262    }};
263);
264
265#[macro_export]
266macro_rules! result_from_gboolean(
267    ($ffi_bool:expr, $($msg:tt)*) =>  {{
268        match ::std::format_args!($($msg)*) {
269            formatted => {
270                if let Some(s) = formatted.as_str() {
271                    $crate::BoolError::from_glib(
272                        $ffi_bool,
273                        s,
274                        file!(),
275                        $crate::function_name!(),
276                        line!(),
277                    )
278                } else {
279                    $crate::BoolError::from_glib(
280                        $ffi_bool,
281                        formatted.to_string(),
282                        file!(),
283                        $crate::function_name!(),
284                        line!(),
285                    )
286                }
287            }
288        }
289
290
291    }};
292);
293
294#[derive(Debug, Clone)]
295pub struct BoolError {
296    pub message: Cow<'static, str>,
297    #[doc(hidden)]
298    pub filename: &'static str,
299    #[doc(hidden)]
300    pub function: &'static str,
301    #[doc(hidden)]
302    pub line: u32,
303}
304
305impl BoolError {
306    pub fn new(
307        message: impl Into<Cow<'static, str>>,
308        filename: &'static str,
309        function: &'static str,
310        line: u32,
311    ) -> Self {
312        Self {
313            message: message.into(),
314            filename,
315            function,
316            line,
317        }
318    }
319
320    pub fn from_glib(
321        b: ffi::gboolean,
322        message: impl Into<Cow<'static, str>>,
323        filename: &'static str,
324        function: &'static str,
325        line: u32,
326    ) -> Result<(), Self> {
327        match b {
328            ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
329            _ => Ok(()),
330        }
331    }
332}
333
334impl fmt::Display for BoolError {
335    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336        f.write_str(&self.message)
337    }
338}
339
340impl error::Error for BoolError {}
341
342#[cfg(test)]
343mod tests {
344    use std::ffi::CString;
345
346    use super::*;
347    use crate::prelude::*;
348
349    #[test]
350    fn test_error_matches() {
351        let e = Error::new(crate::FileError::Failed, "Failed");
352        assert!(e.matches(crate::FileError::Failed));
353        assert!(!e.matches(crate::FileError::Again));
354        assert!(!e.matches(crate::KeyFileError::NotFound));
355    }
356
357    #[test]
358    fn test_error_kind() {
359        let e = Error::new(crate::FileError::Failed, "Failed");
360        assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
361        assert_eq!(e.kind::<crate::KeyFileError>(), None);
362    }
363
364    #[test]
365    fn test_into_raw() {
366        unsafe {
367            let e: *mut ffi::GError =
368                Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
369            assert_eq!((*e).domain, ffi::g_file_error_quark());
370            assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
371            assert_eq!(
372                CStr::from_ptr((*e).message),
373                CString::new("Failed").unwrap().as_c_str()
374            );
375
376            ffi::g_error_free(e);
377        }
378    }
379
380    #[test]
381    fn test_bool_error() {
382        let from_static_msg = bool_error!("Static message");
383        assert_eq!(from_static_msg.to_string(), "Static message");
384
385        let from_dynamic_msg = bool_error!("{} message", "Dynamic");
386        assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
387
388        let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
389        assert!(false_static_res.is_err());
390        let static_err = false_static_res.err().unwrap();
391        assert_eq!(static_err.to_string(), "Static message");
392
393        let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
394        assert!(true_static_res.is_ok());
395
396        let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
397        assert!(false_dynamic_res.is_err());
398        let dynamic_err = false_dynamic_res.err().unwrap();
399        assert_eq!(dynamic_err.to_string(), "Dynamic message");
400
401        let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
402        assert!(true_dynamic_res.is_ok());
403    }
404
405    #[test]
406    fn test_value() {
407        let e1 = Error::new(crate::FileError::Failed, "Failed");
408        // This creates a copy ...
409        let v = e1.to_value();
410        // ... so we have to get the raw pointer from inside the value to check for equality.
411        let ptr = unsafe {
412            crate::gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError
413        };
414
415        let e2 = v.get::<&Error>().unwrap();
416
417        assert_eq!(ptr, e2.to_glib_none().0);
418    }
419
420    #[test]
421    fn test_from_quark() {
422        let original = Error::new(crate::FileError::Failed, "Original message");
423        let modified = Error::with_domain(original.domain(), original.code(), "Modified message");
424
425        // Should have same domain and code
426        assert_eq!(original.domain(), modified.domain());
427        assert_eq!(original.code(), modified.code());
428        assert!(modified.matches(crate::FileError::Failed));
429
430        // But different message
431        assert_eq!(modified.message(), "Modified message");
432        assert_ne!(original.message(), modified.message());
433    }
434
435    #[test]
436    fn test_prefix() {
437        let original = Error::new(crate::FileError::Failed, "Original message");
438        let modified = original.prefix(crate::gstr!("Prefix: "));
439
440        // Should have same domain and code
441        assert!(modified.matches(crate::FileError::Failed));
442
443        // But different message
444        assert_eq!(modified.message(), "Prefix: Original message");
445    }
446}