use alloc::{boxed::Box, string::String, sync::Arc};
macro_rules! err {
($($tt:tt)*) => {{
crate::error::Error::adhoc(alloc::format!($($tt)*))
}}
}
pub(crate) use err;
#[derive(Clone, Debug)]
pub struct Error {
inner: Arc<ErrorInner>,
}
#[derive(Debug)]
struct ErrorInner {
kind: ErrorKind,
cause: Option<Error>,
}
#[derive(Debug)]
enum ErrorKind {
Adhoc(AdhocError),
Range(RangeError),
TimeZoneLookup(TimeZoneLookupError),
FilePath(FilePathError),
IO(IOError),
}
impl Error {
pub(crate) fn adhoc(
err: impl core::fmt::Display + Send + Sync + 'static,
) -> Error {
Error::from(ErrorKind::Adhoc(AdhocError(Box::new(err))))
}
pub(crate) fn unsigned(
what: &'static str,
given: impl Into<u128>,
min: impl Into<i128>,
max: impl Into<i128>,
) -> Error {
Error::from(ErrorKind::Range(RangeError::unsigned(
what, given, min, max,
)))
}
pub(crate) fn signed(
what: &'static str,
given: impl Into<i128>,
min: impl Into<i128>,
max: impl Into<i128>,
) -> Error {
Error::from(ErrorKind::Range(RangeError::signed(
what, given, min, max,
)))
}
pub(crate) fn specific(
what: &'static str,
given: impl Into<i128>,
) -> Error {
Error::from(ErrorKind::Range(RangeError::specific(what, given)))
}
pub(crate) fn time_zone_lookup(name: impl Into<String>) -> Error {
let inner = TimeZoneLookupErrorInner { name: name.into() };
Error::from(ErrorKind::TimeZoneLookup(TimeZoneLookupError(Box::new(
inner,
))))
}
#[cfg(feature = "std")]
pub(crate) fn fs(
path: impl Into<std::path::PathBuf>,
err: std::io::Error,
) -> Error {
Error::io(err).path(path)
}
#[cfg(feature = "std")]
pub(crate) fn io(err: std::io::Error) -> Error {
Error::from(ErrorKind::IO(IOError { err }))
}
#[cfg(feature = "std")]
pub(crate) fn path(self, path: impl Into<std::path::PathBuf>) -> Error {
let err = Error::from(ErrorKind::FilePath(FilePathError {
path: path.into(),
}));
self.context(err)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut err = self;
loop {
write!(f, "{}", err.inner.kind)?;
err = match err.inner.cause.as_ref() {
None => break,
Some(err) => err,
};
write!(f, ": ")?;
}
Ok(())
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
ErrorKind::Adhoc(ref msg) => msg.fmt(f),
ErrorKind::Range(ref err) => err.fmt(f),
ErrorKind::TimeZoneLookup(ref err) => err.fmt(f),
ErrorKind::FilePath(ref err) => err.fmt(f),
ErrorKind::IO(ref err) => err.fmt(f),
}
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { inner: Arc::new(ErrorInner { kind, cause: None }) }
}
}
struct AdhocError(Box<dyn core::fmt::Display + Send + Sync + 'static>);
#[cfg(feature = "std")]
impl std::error::Error for AdhocError {}
impl core::fmt::Display for AdhocError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl core::fmt::Debug for AdhocError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug)]
struct RangeError(Box<RangeErrorKind>);
#[derive(Debug)]
enum RangeErrorKind {
Positive { what: &'static str, given: u128, min: i128, max: i128 },
Negative { what: &'static str, given: i128, min: i128, max: i128 },
Specific { what: &'static str, given: i128 },
}
impl RangeError {
fn unsigned(
what: &'static str,
given: impl Into<u128>,
min: impl Into<i128>,
max: impl Into<i128>,
) -> RangeError {
RangeError(Box::new(RangeErrorKind::Positive {
what,
given: given.into(),
min: min.into(),
max: max.into(),
}))
}
fn signed(
what: &'static str,
given: impl Into<i128>,
min: impl Into<i128>,
max: impl Into<i128>,
) -> RangeError {
RangeError(Box::new(RangeErrorKind::Negative {
what,
given: given.into(),
min: min.into(),
max: max.into(),
}))
}
fn specific(what: &'static str, given: impl Into<i128>) -> RangeError {
RangeError(Box::new(RangeErrorKind::Specific {
what,
given: given.into(),
}))
}
}
#[cfg(feature = "std")]
impl std::error::Error for RangeError {}
impl core::fmt::Display for RangeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self.0 {
RangeErrorKind::Positive { what, given, min, max } => {
write!(
f,
"parameter '{what}' with value {given} \
is not in the required range of {min}..={max}",
)
}
RangeErrorKind::Negative { what, given, min, max } => {
write!(
f,
"parameter '{what}' with value {given} \
is not in the required range of {min}..={max}",
)
}
RangeErrorKind::Specific { what, given } => {
write!(f, "parameter '{what}' with value {given} is illegal",)
}
}
}
}
#[derive(Debug)]
struct TimeZoneLookupError(Box<TimeZoneLookupErrorInner>);
#[derive(Debug)]
struct TimeZoneLookupErrorInner {
name: String,
}
#[cfg(feature = "std")]
impl std::error::Error for TimeZoneLookupError {}
impl core::fmt::Display for TimeZoneLookupError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"failed to find timezone '{}' in time zone database",
self.0.name
)
}
}
struct IOError {
#[cfg(feature = "std")]
err: std::io::Error,
}
#[cfg(feature = "std")]
impl std::error::Error for IOError {}
impl core::fmt::Display for IOError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
write!(f, "{}", self.err)
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
impl core::fmt::Debug for IOError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
f.debug_struct("IOError").field("err", &self.err).finish()
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for IOError {
fn from(err: std::io::Error) -> IOError {
IOError { err }
}
}
struct FilePathError {
#[cfg(feature = "std")]
path: std::path::PathBuf,
}
#[cfg(feature = "std")]
impl std::error::Error for FilePathError {}
impl core::fmt::Display for FilePathError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
write!(f, "{}", self.path.display())
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
impl core::fmt::Debug for FilePathError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
f.debug_struct("FilePathError").field("path", &self.path).finish()
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
pub(crate) trait IntoError {
fn into_error(self) -> Error;
}
impl IntoError for Error {
fn into_error(self) -> Error {
self
}
}
impl IntoError for &'static str {
fn into_error(self) -> Error {
Error::adhoc(self)
}
}
impl IntoError for String {
fn into_error(self) -> Error {
Error::adhoc(self)
}
}
pub(crate) trait ErrorContext {
fn context(self, consequent: impl IntoError) -> Self;
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Self;
}
impl ErrorContext for Error {
fn context(self, consequent: impl IntoError) -> Error {
let mut err = consequent.into_error();
assert!(
err.inner.cause.is_none(),
"cause of consequence must be `None`"
);
Arc::get_mut(&mut err.inner).unwrap().cause = Some(self);
err
}
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Error {
let mut err = consequent().into_error();
assert!(
err.inner.cause.is_none(),
"cause of consequence must be `None`"
);
Arc::get_mut(&mut err.inner).unwrap().cause = Some(self);
err
}
}
impl<T> ErrorContext for Result<T, Error> {
fn context(self, consequent: impl IntoError) -> Result<T, Error> {
self.map_err(|err| err.context(consequent))
}
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Result<T, Error> {
self.map_err(|err| err.with_context(consequent))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_size() {
let expected_size = core::mem::size_of::<usize>();
assert_eq!(expected_size, core::mem::size_of::<Error>());
}
}