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}