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::{translate::*, Quark};
9
10wrapper! {
11 // rustdoc-stripper-ignore-next
12 /// A generic error capable of representing various error domains (types).
13 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
14 #[doc(alias = "GError")]
15 pub struct Error(Boxed<ffi::GError>);
16
17 match fn {
18 copy => |ptr| ffi::g_error_copy(ptr),
19 free => |ptr| ffi::g_error_free(ptr),
20 type_ => || ffi::g_error_get_type(),
21 }
22}
23
24unsafe impl Send for Error {}
25unsafe impl Sync for Error {}
26
27impl Error {
28 // rustdoc-stripper-ignore-next
29 /// Creates an error with supplied error enum variant and message.
30 #[doc(alias = "g_error_new_literal")]
31 #[doc(alias = "g_error_new")]
32 pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error {
33 unsafe {
34 from_glib_full(ffi::g_error_new_literal(
35 T::domain().into_glib(),
36 error.code(),
37 message.to_glib_none().0,
38 ))
39 }
40 }
41
42 // rustdoc-stripper-ignore-next
43 /// Checks if the error domain matches `T`.
44 pub fn is<T: ErrorDomain>(&self) -> bool {
45 self.inner.domain == T::domain().into_glib()
46 }
47
48 // rustdoc-stripper-ignore-next
49 /// Returns the error domain quark
50 pub fn domain(&self) -> Quark {
51 unsafe { from_glib(self.inner.domain) }
52 }
53
54 // rustdoc-stripper-ignore-next
55 /// Checks if the error matches the specified domain and error code.
56 #[doc(alias = "g_error_matches")]
57 pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
58 self.is::<T>() && self.inner.code == err.code()
59 }
60
61 // rustdoc-stripper-ignore-next
62 /// Tries to convert to a specific error enum.
63 ///
64 /// Returns `Some` if the error belongs to the enum's error domain and
65 /// `None` otherwise.
66 ///
67 /// # Examples
68 ///
69 /// ```ignore
70 /// if let Some(file_error) = error.kind::<FileError>() {
71 /// match file_error {
72 /// FileError::Exist => ...
73 /// FileError::Isdir => ...
74 /// ...
75 /// }
76 /// }
77 /// ```
78 pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
79 if self.is::<T>() {
80 T::from(self.inner.code)
81 } else {
82 None
83 }
84 }
85
86 // rustdoc-stripper-ignore-next
87 /// Returns the error message
88 ///
89 /// Most of the time you can simply print the error since it implements the `Display`
90 /// trait, but you can use this method if you need to have the message as a `&str`.
91 pub fn message(&self) -> &str {
92 unsafe {
93 let bytes = CStr::from_ptr(self.inner.message).to_bytes();
94 str::from_utf8(bytes)
95 .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
96 }
97 }
98}
99
100impl fmt::Display for Error {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 f.write_str(self.message())
103 }
104}
105
106impl fmt::Debug for Error {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 f&mut DebugStruct<'_, '_>.debug_struct("Error")
109 .field("domain", unsafe {
110 &crate::Quark::from_glib(self.inner.domain)
111 })
112 .field("code", &self.inner.code)
113 .field(name:"message", &self.message())
114 .finish()
115 }
116}
117
118impl error::Error for Error {}
119
120impl From<Infallible> for Error {
121 fn from(e: Infallible) -> Self {
122 match e {}
123 }
124}
125
126// rustdoc-stripper-ignore-next
127/// `GLib` error domain.
128///
129/// This trait is implemented by error enums that represent error domains (types).
130pub trait ErrorDomain: Copy {
131 // rustdoc-stripper-ignore-next
132 /// Returns the quark identifying the error domain.
133 ///
134 /// As returned from `g_some_error_quark`.
135 fn domain() -> Quark;
136
137 // rustdoc-stripper-ignore-next
138 /// Gets the integer representation of the variant.
139 fn code(self) -> i32;
140
141 // rustdoc-stripper-ignore-next
142 /// Tries to convert an integer code to an enum variant.
143 ///
144 /// By convention, the `Failed` variant, if present, is a catch-all,
145 /// i.e. any unrecognized codes map to it.
146 fn from(code: i32) -> Option<Self>
147 where
148 Self: Sized;
149}
150
151// rustdoc-stripper-ignore-next
152/// Generic error used for functions that fail without any further information
153#[macro_export]
154macro_rules! bool_error(
155// Plain strings
156 ($msg:expr) => {{
157 $crate::BoolError::new(
158 $msg,
159 file!(),
160 $crate::function_name!(),
161 line!(),
162 )
163 }};
164
165// Format strings
166 ($($msg:tt)*) => {{
167 $crate::BoolError::new(
168 format!($($msg)*),
169 file!(),
170 $crate::function_name!(),
171 line!(),
172 )
173 }};
174);
175
176#[macro_export]
177macro_rules! result_from_gboolean(
178// Plain strings
179 ($ffi_bool:expr, $msg:expr) => {{
180 $crate::BoolError::from_glib(
181 $ffi_bool,
182 $msg,
183 file!(),
184 $crate::function_name!(),
185 line!(),
186 )
187 }};
188
189// Format strings
190 ($ffi_bool:expr, $($msg:tt)*) => {{
191 $crate::BoolError::from_glib(
192 $ffi_bool,
193 format!($($msg)*),
194 file!(),
195 $crate::function_name!(),
196 line!(),
197 )
198 }};
199);
200
201#[derive(Debug, Clone)]
202pub struct BoolError {
203 pub message: Cow<'static, str>,
204 #[doc(hidden)]
205 pub filename: &'static str,
206 #[doc(hidden)]
207 pub function: &'static str,
208 #[doc(hidden)]
209 pub line: u32,
210}
211
212impl BoolError {
213 pub fn new(
214 message: impl Into<Cow<'static, str>>,
215 filename: &'static str,
216 function: &'static str,
217 line: u32,
218 ) -> Self {
219 Self {
220 message: message.into(),
221 filename,
222 function,
223 line,
224 }
225 }
226
227 pub fn from_glib(
228 b: ffi::gboolean,
229 message: impl Into<Cow<'static, str>>,
230 filename: &'static str,
231 function: &'static str,
232 line: u32,
233 ) -> Result<(), Self> {
234 match b {
235 ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
236 _ => Ok(()),
237 }
238 }
239}
240
241impl fmt::Display for BoolError {
242 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243 f.write_str(&self.message)
244 }
245}
246
247impl error::Error for BoolError {}
248
249#[cfg(test)]
250mod tests {
251 use std::ffi::CString;
252
253 use super::*;
254 use crate::prelude::*;
255
256 #[test]
257 fn test_error_matches() {
258 let e = Error::new(crate::FileError::Failed, "Failed");
259 assert!(e.matches(crate::FileError::Failed));
260 assert!(!e.matches(crate::FileError::Again));
261 assert!(!e.matches(crate::KeyFileError::NotFound));
262 }
263
264 #[test]
265 fn test_error_kind() {
266 let e = Error::new(crate::FileError::Failed, "Failed");
267 assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
268 assert_eq!(e.kind::<crate::KeyFileError>(), None);
269 }
270
271 #[test]
272 fn test_into_raw() {
273 unsafe {
274 let e: *mut ffi::GError =
275 Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
276 assert_eq!((*e).domain, ffi::g_file_error_quark());
277 assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
278 assert_eq!(
279 CStr::from_ptr((*e).message),
280 CString::new("Failed").unwrap().as_c_str()
281 );
282
283 ffi::g_error_free(e);
284 }
285 }
286
287 #[test]
288 fn test_bool_error() {
289 let from_static_msg = bool_error!("Static message");
290 assert_eq!(from_static_msg.to_string(), "Static message");
291
292 let from_dynamic_msg = bool_error!("{} message", "Dynamic");
293 assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
294
295 let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
296 assert!(false_static_res.is_err());
297 let static_err = false_static_res.err().unwrap();
298 assert_eq!(static_err.to_string(), "Static message");
299
300 let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
301 assert!(true_static_res.is_ok());
302
303 let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
304 assert!(false_dynamic_res.is_err());
305 let dynamic_err = false_dynamic_res.err().unwrap();
306 assert_eq!(dynamic_err.to_string(), "Dynamic message");
307
308 let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
309 assert!(true_dynamic_res.is_ok());
310 }
311
312 #[test]
313 fn test_value() {
314 let e1 = Error::new(crate::FileError::Failed, "Failed");
315 // This creates a copy ...
316 let v = e1.to_value();
317 // ... so we have to get the raw pointer from inside the value to check for equality.
318 let ptr =
319 unsafe { gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError };
320
321 let e2 = v.get::<&Error>().unwrap();
322
323 assert_eq!(ptr, e2.to_glib_none().0);
324 }
325}
326