| 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 | |