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