use std::{borrow::Cow, convert::Infallible, error, ffi::CStr, fmt, str};
use crate::{translate::*, Quark};
wrapper! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[doc(alias = "GError")]
pub struct Error(Boxed<ffi::GError>);
match fn {
copy => |ptr| ffi::g_error_copy(ptr),
free => |ptr| ffi::g_error_free(ptr),
type_ => || ffi::g_error_get_type(),
}
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl Error {
#[doc(alias = "g_error_new_literal")]
#[doc(alias = "g_error_new")]
pub fn new<T: ErrorDomain>(error: T, message: &str) -> Error {
unsafe {
from_glib_full(ffi::g_error_new_literal(
T::domain().into_glib(),
error.code(),
message.to_glib_none().0,
))
}
}
pub fn is<T: ErrorDomain>(&self) -> bool {
self.inner.domain == T::domain().into_glib()
}
pub fn domain(&self) -> Quark {
unsafe { from_glib(self.inner.domain) }
}
#[doc(alias = "g_error_matches")]
pub fn matches<T: ErrorDomain>(&self, err: T) -> bool {
self.is::<T>() && self.inner.code == err.code()
}
pub fn kind<T: ErrorDomain>(&self) -> Option<T> {
if self.is::<T>() {
T::from(self.inner.code)
} else {
None
}
}
pub fn message(&self) -> &str {
unsafe {
let bytes = CStr::from_ptr(self.inner.message).to_bytes();
str::from_utf8(bytes)
.unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.message())
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Error")
.field("domain", unsafe {
&crate::Quark::from_glib(self.inner.domain)
})
.field("code", &self.inner.code)
.field("message", &self.message())
.finish()
}
}
impl error::Error for Error {}
impl From<Infallible> for Error {
fn from(e: Infallible) -> Self {
match e {}
}
}
pub trait ErrorDomain: Copy {
fn domain() -> Quark;
fn code(self) -> i32;
fn from(code: i32) -> Option<Self>
where
Self: Sized;
}
#[macro_export]
macro_rules! bool_error(
($($msg:tt)*) => {{
match ::std::format_args!($($msg)*) {
formatted => {
if let Some(s) = formatted.as_str() {
$crate::BoolError::new(
s,
file!(),
$crate::function_name!(),
line!()
)
} else {
$crate::BoolError::new(
formatted.to_string(),
file!(),
$crate::function_name!(),
line!(),
)
}
}
}
}};
);
#[macro_export]
macro_rules! result_from_gboolean(
($ffi_bool:expr, $($msg:tt)*) => {{
match ::std::format_args!($($msg)*) {
formatted => {
if let Some(s) = formatted.as_str() {
$crate::BoolError::from_glib(
$ffi_bool,
s,
file!(),
$crate::function_name!(),
line!(),
)
} else {
$crate::BoolError::from_glib(
$ffi_bool,
formatted.to_string(),
file!(),
$crate::function_name!(),
line!(),
)
}
}
}
}};
);
#[derive(Debug, Clone)]
pub struct BoolError {
pub message: Cow<'static, str>,
#[doc(hidden)]
pub filename: &'static str,
#[doc(hidden)]
pub function: &'static str,
#[doc(hidden)]
pub line: u32,
}
impl BoolError {
pub fn new(
message: impl Into<Cow<'static, str>>,
filename: &'static str,
function: &'static str,
line: u32,
) -> Self {
Self {
message: message.into(),
filename,
function,
line,
}
}
pub fn from_glib(
b: ffi::gboolean,
message: impl Into<Cow<'static, str>>,
filename: &'static str,
function: &'static str,
line: u32,
) -> Result<(), Self> {
match b {
ffi::GFALSE => Err(BoolError::new(message, filename, function, line)),
_ => Ok(()),
}
}
}
impl fmt::Display for BoolError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.message)
}
}
impl error::Error for BoolError {}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use super::*;
use crate::prelude::*;
#[test]
fn test_error_matches() {
let e = Error::new(crate::FileError::Failed, "Failed");
assert!(e.matches(crate::FileError::Failed));
assert!(!e.matches(crate::FileError::Again));
assert!(!e.matches(crate::KeyFileError::NotFound));
}
#[test]
fn test_error_kind() {
let e = Error::new(crate::FileError::Failed, "Failed");
assert_eq!(e.kind::<crate::FileError>(), Some(crate::FileError::Failed));
assert_eq!(e.kind::<crate::KeyFileError>(), None);
}
#[test]
fn test_into_raw() {
unsafe {
let e: *mut ffi::GError =
Error::new(crate::FileError::Failed, "Failed").into_glib_ptr();
assert_eq!((*e).domain, ffi::g_file_error_quark());
assert_eq!((*e).code, ffi::G_FILE_ERROR_FAILED);
assert_eq!(
CStr::from_ptr((*e).message),
CString::new("Failed").unwrap().as_c_str()
);
ffi::g_error_free(e);
}
}
#[test]
fn test_bool_error() {
let from_static_msg = bool_error!("Static message");
assert_eq!(from_static_msg.to_string(), "Static message");
let from_dynamic_msg = bool_error!("{} message", "Dynamic");
assert_eq!(from_dynamic_msg.to_string(), "Dynamic message");
let false_static_res = result_from_gboolean!(ffi::GFALSE, "Static message");
assert!(false_static_res.is_err());
let static_err = false_static_res.err().unwrap();
assert_eq!(static_err.to_string(), "Static message");
let true_static_res = result_from_gboolean!(ffi::GTRUE, "Static message");
assert!(true_static_res.is_ok());
let false_dynamic_res = result_from_gboolean!(ffi::GFALSE, "{} message", "Dynamic");
assert!(false_dynamic_res.is_err());
let dynamic_err = false_dynamic_res.err().unwrap();
assert_eq!(dynamic_err.to_string(), "Dynamic message");
let true_dynamic_res = result_from_gboolean!(ffi::GTRUE, "{} message", "Dynamic");
assert!(true_dynamic_res.is_ok());
}
#[test]
fn test_value() {
let e1 = Error::new(crate::FileError::Failed, "Failed");
let v = e1.to_value();
let ptr =
unsafe { gobject_ffi::g_value_get_boxed(v.to_glib_none().0) as *const ffi::GError };
let e2 = v.get::<&Error>().unwrap();
assert_eq!(ptr, e2.to_glib_none().0);
}
}