1 // Copyright 2016, The Gtk-rs Project Developers.
2 // See the COPYRIGHT file at the top-level directory of this distribution.
3 // Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4 
5 //! `Error` binding and helper trait.
6 
7 use glib_sys;
8 use std::borrow::Cow;
9 use std::error;
10 use std::ffi::CStr;
11 use std::fmt;
12 use std::str;
13 use translate::*;
14 use Quark;
15 
16 glib_wrapper! {
17     /// A generic error capable of representing various error domains (types).
18     #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
19     pub struct Error(Boxed<glib_sys::GError>);
20 
21     match fn {
22         copy => |ptr| glib_sys::g_error_copy(ptr),
23         free => |ptr| glib_sys::g_error_free(ptr),
24         get_type => || glib_sys::g_error_get_type(),
25     }
26 }
27 
28 unsafe impl Send for Error {}
29 unsafe impl Sync for Error {}
30 
31 impl Error {
32     /// Creates an error with supplied error enum variant and message.
new<T: ErrorDomain>(error: T, message: &str) -> Error33     pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error {
34         unsafe {
35             from_glib_full(glib_sys::g_error_new_literal(
36                 T::domain().to_glib(),
37                 error.code(),
38                 message.to_glib_none().0,
39             ))
40         }
41     }
42 
43     /// Checks if the error domain matches `T`.
is<T: ErrorDomain>(&self) -> bool44     pub fn is<T: ErrorDomain>(&self) -> bool {
45         self.0.domain == T::domain().to_glib()
46     }
47 
48     /// Tries to convert to a specific error enum.
49     ///
50     /// Returns `Some` if the error belongs to the enum's error domain and
51     /// `None` otherwise.
52     ///
53     /// # Examples
54     ///
55     /// ```ignore
56     /// if let Some(file_error) = error.kind::<FileError>() {
57     ///     match file_error {
58     ///         FileError::Exist => ...
59     ///         FileError::Isdir => ...
60     ///         ...
61     ///     }
62     /// }
63     /// ```
64     ///
65     /// ```ignore
66     /// match error {
67     ///     Some(FileError::Exist) => ...
68     ///     Some(FileError::Isdir) => ...
69     ///     ...
70     /// }
71     /// ```
kind<T: ErrorDomain>(&self) -> Option<T>72     pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
73         if self.0.domain == T::domain().to_glib() {
74             T::from(self.0.code)
75         } else {
76             None
77         }
78     }
79 
message(&self) -> &str80     fn message(&self) -> &str {
81         unsafe {
82             let bytes = CStr::from_ptr(self.0.message).to_bytes();
83             str::from_utf8(bytes)
84                 .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
85         }
86     }
87 }
88 
89 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result90     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91         f.write_str(self.message())
92     }
93 }
94 
95 impl error::Error for Error {
description(&self) -> &str96     fn description(&self) -> &str {
97         self.message()
98     }
99 }
100 
101 impl fmt::Debug for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result102     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103         f.debug_struct("Error")
104             .field("domain", &::Quark::from_glib(self.0.domain))
105             .field("code", &self.0.code)
106             .field("message", &self.message())
107             .finish()
108     }
109 }
110 
111 /// `GLib` error domain.
112 ///
113 /// This trait is implemented by error enums that represent error domains (types).
114 pub trait ErrorDomain: Copy {
115     /// Returns the quark identifying the error domain.
116     ///
117     /// As returned from `g_some_error_quark`.
domain() -> Quark118     fn domain() -> Quark;
119 
120     /// Gets the integer representation of the variant.
code(self) -> i32121     fn code(self) -> i32;
122 
123     /// Tries to convert an integer code to an enum variant.
124     ///
125     /// By convention, the `Failed` variant, if present, is a catch-all,
126     /// i.e. any unrecognized codes map to it.
from(code: i32) -> Option<Self> where Self: Sized127     fn from(code: i32) -> Option<Self>
128     where
129         Self: Sized;
130 }
131 
132 /// Generic error used for functions that fail without any further information
133 #[macro_export]
134 macro_rules! glib_bool_error(
135 // Plain strings
136     ($msg:expr) =>  {
137         $crate::BoolError::new($msg, file!(), module_path!(), line!())
138     };
139 
140 // Format strings
141     ($($msg:tt)*) =>  { {
142         $crate::BoolError::new(format!($($msg)*), file!(), module_path!(), line!())
143     }};
144 );
145 
146 #[macro_export]
147 macro_rules! glib_result_from_gboolean(
148 // Plain strings
149     ($ffi_bool:expr, $msg:expr) =>  {
150         $crate::BoolError::from_glib($ffi_bool, $msg, file!(), module_path!(), line!())
151     };
152 
153 // Format strings
154     ($ffi_bool:expr, $($msg:tt)*) =>  { {
155         $crate::BoolError::from_glib(
156             $ffi_bool,
157             format!($($msg)*),
158             file!(),
159             module_path!(),
160             line!(),
161         )
162     }};
163 );
164 
165 #[derive(Debug, Clone)]
166 pub struct BoolError {
167     pub message: Cow<'static, str>,
168     #[doc(hidden)]
169     pub filename: &'static str,
170     #[doc(hidden)]
171     pub function: &'static str,
172     #[doc(hidden)]
173     pub line: u32,
174 }
175 
176 impl BoolError {
new<Msg: Into<Cow<'static, str>>>( message: Msg, filename: &'static str, function: &'static str, line: u32, ) -> Self177     pub fn new<Msg: Into<Cow<'static, str>>>(
178         message: Msg,
179         filename: &'static str,
180         function: &'static str,
181         line: u32,
182     ) -> Self {
183         BoolError {
184             message: message.into(),
185             filename,
186             function,
187             line,
188         }
189     }
190 
from_glib<Msg: Into<Cow<'static, str>>>( b: glib_sys::gboolean, message: Msg, filename: &'static str, function: &'static str, line: u32, ) -> Result<(), Self>191     pub fn from_glib<Msg: Into<Cow<'static, str>>>(
192         b: glib_sys::gboolean,
193         message: Msg,
194         filename: &'static str,
195         function: &'static str,
196         line: u32,
197     ) -> Result<(), Self> {
198         match b {
199             glib_sys::GFALSE => Err(BoolError::new(message, filename, function, line)),
200             _ => Ok(()),
201         }
202     }
203 }
204 
205 impl fmt::Display for BoolError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result206     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207         write!(
208             f,
209             "Error {:?} in {:?} at {}:{}",
210             self.message, self.function, self.filename, self.line
211         )
212     }
213 }
214 
215 impl error::Error for BoolError {
description(&self) -> &str216     fn description(&self) -> &str {
217         self.message.as_ref()
218     }
219 }
220 
221 #[cfg(test)]
222 mod tests {
223     use super::*;
224 
225     #[test]
test_bool_error()226     fn test_bool_error() {
227         use std::error::Error;
228 
229         let from_static_msg = glib_bool_error!("Static message");
230         assert_eq!(from_static_msg.description(), "Static message");
231 
232         let from_dynamic_msg = glib_bool_error!("{} message", "Dynamic");
233         assert_eq!(from_dynamic_msg.description(), "Dynamic message");
234 
235         let false_static_res = glib_result_from_gboolean!(glib_sys::GFALSE, "Static message");
236         assert!(false_static_res.is_err());
237         let static_err = false_static_res.err().unwrap();
238         assert_eq!(static_err.description(), "Static message");
239 
240         let true_static_res = glib_result_from_gboolean!(glib_sys::GTRUE, "Static message");
241         assert!(true_static_res.is_ok());
242 
243         let false_dynamic_res =
244             glib_result_from_gboolean!(glib_sys::GFALSE, "{} message", "Dynamic");
245         assert!(false_dynamic_res.is_err());
246         let dynamic_err = false_dynamic_res.err().unwrap();
247         assert_eq!(dynamic_err.description(), "Dynamic message");
248 
249         let true_dynamic_res = glib_result_from_gboolean!(glib_sys::GTRUE, "{} message", "Dynamic");
250         assert!(true_dynamic_res.is_ok());
251     }
252 }
253