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 | |
6 | use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str}; |
7 | |
8 | use crate::{ffi, translate::*, Quark}; |
9 | |
10 | wrapper! { |
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 | |
24 | unsafe impl Send for Error {} |
25 | unsafe impl Sync for Error {} |
26 | |
27 | impl 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 | |
100 | impl fmt::Display for Error { |
101 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
102 | f.write_str(self.message()) |
103 | } |
104 | } |
105 | |
106 | impl 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 | |
118 | impl error::Error for Error {} |
119 | |
120 | impl 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). |
130 | pub 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 ] |
154 | macro_rules! bool_error( |
155 | ($($msg:tt)*) => {{ |
156 | match ::std::format_args!($($msg)*) { |
157 | formatted => { |
158 | if let Some(s) = formatted.as_str() { |
159 | $crate::BoolError::new( |
160 | s, |
161 | file!(), |
162 | $crate::function_name!(), |
163 | line!() |
164 | ) |
165 | } else { |
166 | $crate::BoolError::new( |
167 | formatted.to_string(), |
168 | file!(), |
169 | $crate::function_name!(), |
170 | line!(), |
171 | ) |
172 | } |
173 | } |
174 | } |
175 | }}; |
176 | ); |
177 | |
178 | #[macro_export ] |
179 | macro_rules! result_from_gboolean( |
180 | ($ffi_bool:expr, $($msg:tt)*) => {{ |
181 | match ::std::format_args!($($msg)*) { |
182 | formatted => { |
183 | if let Some(s) = formatted.as_str() { |
184 | $crate::BoolError::from_glib( |
185 | $ffi_bool, |
186 | s, |
187 | file!(), |
188 | $crate::function_name!(), |
189 | line!(), |
190 | ) |
191 | } else { |
192 | $crate::BoolError::from_glib( |
193 | $ffi_bool, |
194 | formatted.to_string(), |
195 | file!(), |
196 | $crate::function_name!(), |
197 | line!(), |
198 | ) |
199 | } |
200 | } |
201 | } |
202 | |
203 | |
204 | }}; |
205 | ); |
206 | |
207 | #[derive (Debug, Clone)] |
208 | pub struct BoolError { |
209 | pub message: Cow<'static, str>, |
210 | #[doc (hidden)] |
211 | pub filename: &'static str, |
212 | #[doc (hidden)] |
213 | pub function: &'static str, |
214 | #[doc (hidden)] |
215 | pub line: u32, |
216 | } |
217 | |
218 | impl BoolError { |
219 | pub fn new( |
220 | message: impl Into<Cow<'static, str>>, |
221 | filename: &'static str, |
222 | function: &'static str, |
223 | line: u32, |
224 | ) -> Self { |
225 | Self { |
226 | message: message.into(), |
227 | filename, |
228 | function, |
229 | line, |
230 | } |
231 | } |
232 | |
233 | pub fn from_glib( |
234 | b: ffi::gboolean, |
235 | message: impl Into<Cow<'static, str>>, |
236 | filename: &'static str, |
237 | function: &'static str, |
238 | line: u32, |
239 | ) -> Result<(), Self> { |
240 | match b { |
241 | ffi::GFALSE => Err(BoolError::new(message, filename, function, line)), |
242 | _ => Ok(()), |
243 | } |
244 | } |
245 | } |
246 | |
247 | impl fmt::Display for BoolError { |
248 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
249 | f.write_str(&self.message) |
250 | } |
251 | } |
252 | |
253 | impl error::Error for BoolError {} |
254 | |
255 | #[cfg (test)] |
256 | mod tests { |
257 | use std::ffi::CString; |
258 | |
259 | use super::*; |
260 | use crate::prelude::*; |
261 | |
262 | #[test ] |
263 | fn test_error_matches() { |
264 | let e = Error::new(crate::FileError::Failed, "Failed" ); |
265 | assert!(e.matches(crate::FileError::Failed)); |
266 | assert!(!e.matches(crate::FileError::Again)); |
267 | assert!(!e.matches(crate::KeyFileError::NotFound)); |
268 | } |
269 | |
270 | #[test ] |
271 | fn test_error_kind() { |
272 | let e = Error::new(crate::FileError::Failed, "Failed" ); |
273 | assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed)); |
274 | assert_eq!(e.kind::<crate::KeyFileError>(), None); |
275 | } |
276 | |
277 | #[test ] |
278 | fn test_into_raw() { |
279 | unsafe { |
280 | let e: *mut ffi::GError = |
281 | Error::new(crate::FileError::Failed, "Failed" ).into_glib_ptr(); |
282 | assert_eq!((*e).domain, ffi::g_file_error_quark()); |
283 | assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED); |
284 | assert_eq!( |
285 | CStr::from_ptr((*e).message), |
286 | CString::new("Failed" ).unwrap().as_c_str() |
287 | ); |
288 | |
289 | ffi::g_error_free(e); |
290 | } |
291 | } |
292 | |
293 | #[test ] |
294 | fn test_bool_error() { |
295 | let from_static_msg = bool_error!("Static message" ); |
296 | assert_eq!(from_static_msg.to_string(), "Static message" ); |
297 | |
298 | let from_dynamic_msg = bool_error!("{} message" , "Dynamic" ); |
299 | assert_eq!(from_dynamic_msg.to_string(), "Dynamic message" ); |
300 | |
301 | let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message" ); |
302 | assert!(false_static_res.is_err()); |
303 | let static_err = false_static_res.err().unwrap(); |
304 | assert_eq!(static_err.to_string(), "Static message" ); |
305 | |
306 | let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message" ); |
307 | assert!(true_static_res.is_ok()); |
308 | |
309 | let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message" , "Dynamic" ); |
310 | assert!(false_dynamic_res.is_err()); |
311 | let dynamic_err = false_dynamic_res.err().unwrap(); |
312 | assert_eq!(dynamic_err.to_string(), "Dynamic message" ); |
313 | |
314 | let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message" , "Dynamic" ); |
315 | assert!(true_dynamic_res.is_ok()); |
316 | } |
317 | |
318 | #[test ] |
319 | fn test_value() { |
320 | let e1 = Error::new(crate::FileError::Failed, "Failed" ); |
321 | // This creates a copy ... |
322 | let v = e1.to_value(); |
323 | // ... so we have to get the raw pointer from inside the value to check for equality. |
324 | let ptr = unsafe { |
325 | crate::gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError |
326 | }; |
327 | |
328 | let e2 = v.get::<&Error>().unwrap(); |
329 | |
330 | assert_eq!(ptr, e2.to_glib_none().0); |
331 | } |
332 | } |
333 | |