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