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::{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 | // 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 ] |
177 | macro_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)] |
202 | pub 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 | |
212 | impl 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 | |
241 | impl fmt::Display for BoolError { |
242 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
243 | f.write_str(&self.message) |
244 | } |
245 | } |
246 | |
247 | impl error::Error for BoolError {} |
248 | |
249 | #[cfg (test)] |
250 | mod 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 | |