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