use std::os::raw::c_char;
use std::panic::{self, RefUnwindSafe};
use std::path::Path;
use chemfiles_sys as ffi;
use crate::strings;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub status: Status,
pub message: String,
}
#[repr(C)]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Status {
Success = ffi::chfl_status::CHFL_SUCCESS as isize,
MemoryError = ffi::chfl_status::CHFL_MEMORY_ERROR as isize,
FileError = ffi::chfl_status::CHFL_FILE_ERROR as isize,
FormatError = ffi::chfl_status::CHFL_FORMAT_ERROR as isize,
SelectionError = ffi::chfl_status::CHFL_SELECTION_ERROR as isize,
ConfigurationError = ffi::chfl_status::CHFL_CONFIGURATION_ERROR as isize,
OutOfBounds = ffi::chfl_status::CHFL_OUT_OF_BOUNDS as isize,
PropertyError = ffi::chfl_status::CHFL_PROPERTY_ERROR as isize,
ChemfilesError = ffi::chfl_status::CHFL_GENERIC_ERROR as isize,
StdCppError = ffi::chfl_status::CHFL_CXX_ERROR as isize,
UTF8PathError,
}
impl From<ffi::chfl_status> for Error {
fn from(status: ffi::chfl_status) -> Error {
let status = match status {
ffi::chfl_status::CHFL_SUCCESS => Status::Success,
ffi::chfl_status::CHFL_CXX_ERROR => Status::StdCppError,
ffi::chfl_status::CHFL_GENERIC_ERROR => Status::ChemfilesError,
ffi::chfl_status::CHFL_MEMORY_ERROR => Status::MemoryError,
ffi::chfl_status::CHFL_FILE_ERROR => Status::FileError,
ffi::chfl_status::CHFL_FORMAT_ERROR => Status::FormatError,
ffi::chfl_status::CHFL_SELECTION_ERROR => Status::SelectionError,
ffi::chfl_status::CHFL_CONFIGURATION_ERROR => Status::ConfigurationError,
ffi::chfl_status::CHFL_OUT_OF_BOUNDS => Status::OutOfBounds,
ffi::chfl_status::CHFL_PROPERTY_ERROR => Status::PropertyError,
};
let message = Error::last_error();
Error { status, message }
}
}
impl From<std::str::Utf8Error> for Error {
fn from(_: std::str::Utf8Error) -> Self {
Error {
status: Status::UTF8PathError,
message: "failed to convert data to UTF8 string".into(),
}
}
}
impl Error {
pub(crate) fn utf8_path_error(path: &Path) -> Error {
Error {
status: Status::UTF8PathError,
message: format!("Could not convert '{}' to UTF8", path.display()),
}
}
pub fn last_error() -> String {
unsafe { strings::from_c(ffi::chfl_last_error()) }
}
pub fn cleanup() {
unsafe {
check(ffi::chfl_clear_errors()).expect("error in ffi::chfl_clear_errors. Things went very bad");
}
}
}
pub(crate) fn check(status: ffi::chfl_status) -> Result<(), Error> {
if status == ffi::chfl_status::CHFL_SUCCESS {
Ok(())
} else {
Err(Error::from(status))
}
}
pub(crate) fn check_success(status: ffi::chfl_status) {
assert!(
status == ffi::chfl_status::CHFL_SUCCESS,
"unexpected failure: {}",
Error::last_error()
);
}
pub(crate) fn check_not_null<T>(ptr: *const T) {
assert!(!ptr.is_null(), "unexpected null pointer: {}", Error::last_error());
}
pub trait WarningCallback: RefUnwindSafe + Fn(&str) {}
impl<T> WarningCallback for T where T: RefUnwindSafe + Fn(&str) {}
static mut LOGGING_CALLBACK: Option<*mut dyn WarningCallback<Output = ()>> = None;
extern "C" fn warning_callback(message: *const c_char) {
unsafe {
let callback = &*LOGGING_CALLBACK.expect("No callback provided, this is an internal bug");
let _result = panic::catch_unwind(|| {
callback(&strings::from_c(message));
});
}
}
pub fn set_warning_callback<F>(callback: F)
where
F: WarningCallback + 'static,
{
let callback = Box::into_raw(Box::new(callback));
unsafe {
if let Some(previous) = LOGGING_CALLBACK {
let previous = Box::from_raw(previous);
std::mem::drop(previous);
LOGGING_CALLBACK = Some(callback);
} else {
LOGGING_CALLBACK = Some(callback);
check_success(ffi::chfl_set_warning_callback(warning_callback));
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(fmt, "{}", self.message)
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match self.status {
Status::Success => "Success",
Status::StdCppError => "Exception from the C++ standard library",
Status::ChemfilesError => "Exception from the chemfiles library",
Status::MemoryError => "Error in memory allocations",
Status::FileError => "Error while reading or writing a file",
Status::FormatError => "Error in file formatting, i.e. the file is invalid",
Status::SelectionError => "Error in selection string syntax",
Status::UTF8PathError => "A string is not valid UTF8",
Status::ConfigurationError => "Error in configuration files",
Status::OutOfBounds => "Out of bounds indexing",
Status::PropertyError => "Error in property",
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Trajectory;
#[test]
fn errors() {
Error::cleanup();
assert_eq!(Error::last_error(), "");
assert!(Trajectory::open("nope", 'r').is_err());
assert_eq!(
Error::last_error(),
"file at \'nope\' does not have an extension, provide a format name to read it"
);
Error::cleanup();
assert_eq!(Error::last_error(), "");
}
#[test]
fn codes() {
assert_eq!(Error::from(ffi::chfl_status::CHFL_SUCCESS).status, Status::Success);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_CXX_ERROR).status,
Status::StdCppError
);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_GENERIC_ERROR).status,
Status::ChemfilesError
);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_MEMORY_ERROR).status,
Status::MemoryError
);
assert_eq!(Error::from(ffi::chfl_status::CHFL_FILE_ERROR).status, Status::FileError);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_FORMAT_ERROR).status,
Status::FormatError
);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_SELECTION_ERROR).status,
Status::SelectionError
);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_OUT_OF_BOUNDS).status,
Status::OutOfBounds
);
assert_eq!(
Error::from(ffi::chfl_status::CHFL_PROPERTY_ERROR).status,
Status::PropertyError
);
}
}