1use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str};
7
8use crate::{ffi, translate::*, Quark};
9
10wrapper! {
11 #[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 #[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 #[doc(alias = "g_error_new_literal")]
59 pub fn with_domain(domain: Quark, code: i32, message: &str) -> Error {
60 unsafe {
61 from_glib_full(ffi::g_error_new_literal(
62 domain.into_glib(),
63 code,
64 message.to_glib_none().0,
65 ))
66 }
67 }
68
69 pub fn is<T: ErrorDomain>(&self) -> bool {
72 self.inner.domain == T::domain().into_glib()
73 }
74
75 pub fn domain(&self) -> Quark {
78 unsafe { from_glib(self.inner.domain) }
79 }
80
81 pub fn code(&self) -> i32 {
84 self.inner.code
85 }
86
87 #[doc(alias = "g_error_matches")]
90 pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
91 self.is::<T>() && self.inner.code == err.code()
92 }
93
94 pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
112 if self.is::<T>() {
113 T::from(self.inner.code)
114 } else {
115 None
116 }
117 }
118
119 pub fn message(&self) -> &str {
125 unsafe {
126 let bytes = CStr::from_ptr(self.inner.message).to_bytes();
127 str::from_utf8(bytes)
128 .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
129 }
130 }
131}
132
133impl fmt::Display for Error {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 f.write_str(self.message())
136 }
137}
138
139impl fmt::Debug for Error {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 f.debug_struct("Error")
142 .field("domain", unsafe {
143 &crate::Quark::from_glib(self.inner.domain)
144 })
145 .field("code", &self.inner.code)
146 .field("message", &self.message())
147 .finish()
148 }
149}
150
151impl error::Error for Error {}
152
153impl From<Infallible> for Error {
154 fn from(e: Infallible) -> Self {
155 match e {}
156 }
157}
158
159pub trait ErrorDomain: Copy {
164 fn domain() -> Quark;
169
170 fn code(self) -> i32;
173
174 fn from(code: i32) -> Option<Self>
180 where
181 Self: Sized;
182}
183
184#[macro_export]
187macro_rules! bool_error(
188 ($($msg:tt)*) => {{
189 match ::std::format_args!($($msg)*) {
190 formatted => {
191 if let Some(s) = formatted.as_str() {
192 $crate::BoolError::new(
193 s,
194 file!(),
195 $crate::function_name!(),
196 line!()
197 )
198 } else {
199 $crate::BoolError::new(
200 formatted.to_string(),
201 file!(),
202 $crate::function_name!(),
203 line!(),
204 )
205 }
206 }
207 }
208 }};
209);
210
211#[macro_export]
212macro_rules! result_from_gboolean(
213 ($ffi_bool:expr, $($msg:tt)*) => {{
214 match ::std::format_args!($($msg)*) {
215 formatted => {
216 if let Some(s) = formatted.as_str() {
217 $crate::BoolError::from_glib(
218 $ffi_bool,
219 s,
220 file!(),
221 $crate::function_name!(),
222 line!(),
223 )
224 } else {
225 $crate::BoolError::from_glib(
226 $ffi_bool,
227 formatted.to_string(),
228 file!(),
229 $crate::function_name!(),
230 line!(),
231 )
232 }
233 }
234 }
235
236
237 }};
238);
239
240#[derive(Debug, Clone)]
241pub struct BoolError {
242 pub message: Cow<'static, str>,
243 #[doc(hidden)]
244 pub filename: &'static str,
245 #[doc(hidden)]
246 pub function: &'static str,
247 #[doc(hidden)]
248 pub line: u32,
249}
250
251impl BoolError {
252 pub fn new(
253 message: impl Into<Cow<'static, str>>,
254 filename: &'static str,
255 function: &'static str,
256 line: u32,
257 ) -> Self {
258 Self {
259 message: message.into(),
260 filename,
261 function,
262 line,
263 }
264 }
265
266 pub fn from_glib(
267 b: ffi::gboolean,
268 message: impl Into<Cow<'static, str>>,
269 filename: &'static str,
270 function: &'static str,
271 line: u32,
272 ) -> Result<(), Self> {
273 match b {
274 ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
275 _ => Ok(()),
276 }
277 }
278}
279
280impl fmt::Display for BoolError {
281 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282 f.write_str(&self.message)
283 }
284}
285
286impl error::Error for BoolError {}
287
288#[cfg(test)]
289mod tests {
290 use std::ffi::CString;
291
292 use super::*;
293 use crate::prelude::*;
294
295 #[test]
296 fn test_error_matches() {
297 let e = Error::new(crate::FileError::Failed, "Failed");
298 assert!(e.matches(crate::FileError::Failed));
299 assert!(!e.matches(crate::FileError::Again));
300 assert!(!e.matches(crate::KeyFileError::NotFound));
301 }
302
303 #[test]
304 fn test_error_kind() {
305 let e = Error::new(crate::FileError::Failed, "Failed");
306 assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
307 assert_eq!(e.kind::<crate::KeyFileError>(), None);
308 }
309
310 #[test]
311 fn test_into_raw() {
312 unsafe {
313 let e: *mut ffi::GError =
314 Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
315 assert_eq!((*e).domain, ffi::g_file_error_quark());
316 assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
317 assert_eq!(
318 CStr::from_ptr((*e).message),
319 CString::new("Failed").unwrap().as_c_str()
320 );
321
322 ffi::g_error_free(e);
323 }
324 }
325
326 #[test]
327 fn test_bool_error() {
328 let from_static_msg = bool_error!("Static message");
329 assert_eq!(from_static_msg.to_string(), "Static message");
330
331 let from_dynamic_msg = bool_error!("{} message", "Dynamic");
332 assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
333
334 let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
335 assert!(false_static_res.is_err());
336 let static_err = false_static_res.err().unwrap();
337 assert_eq!(static_err.to_string(), "Static message");
338
339 let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
340 assert!(true_static_res.is_ok());
341
342 let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
343 assert!(false_dynamic_res.is_err());
344 let dynamic_err = false_dynamic_res.err().unwrap();
345 assert_eq!(dynamic_err.to_string(), "Dynamic message");
346
347 let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
348 assert!(true_dynamic_res.is_ok());
349 }
350
351 #[test]
352 fn test_value() {
353 let e1 = Error::new(crate::FileError::Failed, "Failed");
354 let v = e1.to_value();
356 let ptr = unsafe {
358 crate::gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError
359 };
360
361 let e2 = v.get::<&Error>().unwrap();
362
363 assert_eq!(ptr, e2.to_glib_none().0);
364 }
365
366 #[test]
367 fn test_from_quark() {
368 let original = Error::new(crate::FileError::Failed, "Original message");
369 let modified = Error::with_domain(original.domain(), original.code(), "Modified message");
370
371 assert_eq!(original.domain(), modified.domain());
373 assert_eq!(original.code(), modified.code());
374 assert!(modified.matches(crate::FileError::Failed));
375
376 assert_eq!(modified.message(), "Modified message");
378 assert_ne!(original.message(), modified.message());
379 }
380}