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}