use crate::error::Error as GitOidError;
use core::any::Any;
use core::cell::RefCell;
use core::fmt::Display;
use core::fmt::Formatter;
use core::fmt::Result as FmtResult;
use core::panic::UnwindSafe;
use core::str::Utf8Error;
use std::error::Error as StdError;
use std::ffi::NulError;
use std::panic::catch_unwind;
use url::ParseError as UrlError;
thread_local! {
#[doc(hidden)]
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
}
#[inline]
pub(crate) fn set_error_msg(e: String) {
LAST_ERROR.with(|last| {
*last.borrow_mut() = Some(e);
});
}
#[inline]
pub(crate) fn get_error_msg() -> Option<String> {
LAST_ERROR.with(|last| last.borrow_mut().take())
}
pub(crate) fn catch_panic<T, F>(f: F) -> Option<T>
where
F: FnOnce() -> Result<T, Error> + UnwindSafe,
{
let result = catch_unwind(f);
match result {
Ok(Ok(value)) => Some(value),
Ok(Err(err)) => {
set_error_msg(match err.source() {
None => err.to_string(),
Some(source_err) => {
format!("{}: {}", err, source_err)
}
});
None
}
Err(err) => {
set_error_msg(ErrorMsg::from(err).to_string());
None
}
}
}
#[derive(Debug)]
pub(crate) enum Error {
ContentPtrIsNull,
StringPtrIsNull,
GitOidPtrIsNull,
Utf8UnexpectedEnd,
Utf8InvalidByte(usize, usize),
NotValidUrl(UrlError),
NotGitOidUrl(GitOidError),
StringHadInteriorNul(usize),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Error::ContentPtrIsNull => write!(f, "data pointer is null"),
Error::StringPtrIsNull => write!(f, "string pointer is null"),
Error::GitOidPtrIsNull => write!(f, "GitOID pointer is null"),
Error::Utf8UnexpectedEnd => write!(f, "UTF-8 byte sequence ended unexpectedly"),
Error::Utf8InvalidByte(start, len) => write!(
f,
"invalid {}-byte UTF-8 sequence, starting at byte {}",
len, start
),
Error::NotValidUrl(_) => write!(f, "string is not a valid URL"),
Error::NotGitOidUrl(_) => write!(f, "string is not a valid GitOID URL"),
Error::StringHadInteriorNul(loc) => {
write!(f, "string had interior NUL at byte {}", loc)
}
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::NotValidUrl(e) => Some(e),
Error::NotGitOidUrl(e) => Some(e),
_ => None,
}
}
}
impl From<Utf8Error> for Error {
fn from(utf8_error: Utf8Error) -> Error {
match utf8_error.error_len() {
None => Error::Utf8UnexpectedEnd,
Some(len) => Error::Utf8InvalidByte(utf8_error.valid_up_to(), len),
}
}
}
impl From<UrlError> for Error {
fn from(url_error: UrlError) -> Error {
Error::NotValidUrl(url_error)
}
}
impl From<GitOidError> for Error {
fn from(gitoid_error: GitOidError) -> Error {
Error::NotGitOidUrl(gitoid_error)
}
}
impl From<NulError> for Error {
fn from(nul_error: NulError) -> Error {
Error::StringHadInteriorNul(nul_error.nul_position())
}
}
#[derive(Debug)]
enum ErrorMsg {
Known(String),
Unknown,
}
impl Display for ErrorMsg {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
ErrorMsg::Known(s) => write!(f, "{}", s),
ErrorMsg::Unknown => write!(f, "an unknown error occured"),
}
}
}
impl From<Box<dyn Any + Send + 'static>> for ErrorMsg {
fn from(other: Box<dyn Any + Send + 'static>) -> ErrorMsg {
if let Some(s) = other.downcast_ref::<String>() {
ErrorMsg::Known(s.clone())
} else if let Some(s) = other.downcast_ref::<&str>() {
ErrorMsg::Known(s.to_string())
} else {
ErrorMsg::Unknown
}
}
}