apperr 0.2.0

A thin special-purpose wrapper around Any.
Documentation
//! _AppErr_ is a wrapper object intended to hold application-specific errors.
//!
//! The archetypal use-case is to allow applications that call library
//! runtimes which call application callbacks to allow the application
//! callbacks to return application-specific errors back to itself through the
//! runtime.
//!
//! In order to lessen the risk of wrapping an unintended type, the [`AppErr`]
//! constructor only take in types that implement [`apperr::Blessed`](Blessed)
//! (which is a subtrait of [`std::error::Error`]).
//!
//! # Alternatives
//! There are other ways to solve the same problem, but they are not always
//! feasible.
//! - Traits can use associated types to declare the application-specific error
//!   types.
//! - A generic parameter can be used to declare the application-specific error
//!   type.
//! - Global variables can be used to store error information.

use std::any::Any;

/// Marker trait used to bless a type so that it can be used as a
/// application-specific error.
pub trait Blessed: std::error::Error {}

/// An error type used to pass an application-specific error through a library
/// runtime.
///
/// Application callbacks can return this type for the `Err()` case in order
/// to allow application-defined errors to be passed back to the application
/// through a library runtime.
#[repr(transparent)]
#[derive(Debug)]
pub struct AppErr(Box<dyn Any + Send + 'static>);

impl AppErr {
  /// Create a new application callback error object that can be converted back
  /// into to its original type (as long as it is [`Any`] compatible).
  ///
  /// ```
  /// use std::fmt;
  /// use apperr::{AppErr, Blessed};
  ///
  /// #[derive(Debug)]
  /// enum MyErr {
  ///   Something(String)
  /// }
  /// impl fmt::Display for MyErr {
  ///   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  ///     match self {
  ///       MyErr::Something(s) => {
  ///         write!(f, "Some error; {}", s)
  ///       }
  ///     }
  ///   }
  /// }
  /// impl std::error::Error for MyErr { }
  /// impl Blessed for MyErr { }
  ///
  /// let apperr = AppErr::new(MyErr::Something("hello".into()));
  /// ```
  pub fn new<E>(e: E) -> Self
  where
    E: Blessed + Send + 'static
  {
    Self(Box::new(e))
  }

  /// Inspect error type wrapped by the `AppErr`.
  ///
  /// ```
  /// use std::fmt;
  /// use apperr::{AppErr, Blessed};
  ///
  /// #[derive(Debug)]
  /// enum MyErr {
  ///   Something(String)
  /// }
  /// impl fmt::Display for MyErr {
  ///   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  ///     match self {
  ///       MyErr::Something(s) => {
  ///         write!(f, "Some error; {}", s)
  ///       }
  ///     }
  ///   }
  /// }
  /// impl std::error::Error for MyErr { }
  /// impl Blessed for MyErr { }
  ///
  /// let apperr = AppErr::new(MyErr::Something("hello".into()));
  ///
  /// assert!(apperr.is::<MyErr>());
  /// assert_eq!(apperr.is::<String>(), false);
  /// ```
  pub fn is<T>(&self) -> bool
  where
    T: Any
  {
    self.0.is::<T>()
  }

  /// Attempt to unpack and cast the inner error type.
  ///
  /// If it can't be downcast to `E`, the `AppErr` will be returned back to the
  /// caller in the `Err()` case.
  ///
  /// ```
  /// use std::fmt;
  /// use apperr::{AppErr, Blessed};
  ///
  /// #[derive(Debug)]
  /// enum MyErr {
  ///   Something(String)
  /// }
  /// impl fmt::Display for MyErr {
  ///   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  ///     match self {
  ///       MyErr::Something(s) => {
  ///         write!(f, "Some error; {}", s)
  ///       }
  ///     }
  ///   }
  /// }
  /// impl std::error::Error for MyErr { }
  /// impl Blessed for MyErr { }
  ///
  /// let apperr = AppErr::new(MyErr::Something("hello".into()));
  ///
  /// let Ok(e) = apperr.try_into_inner::<MyErr>() else {
  ///   panic!("Unexpectedly not MyErr");
  /// };
  /// ```
  pub fn try_into_inner<E: 'static>(self) -> Result<E, AppErr> {
    match self.0.downcast::<E>() {
      Ok(e) => Ok(*e),
      Err(e) => Err(AppErr(e))
    }
  }

  /// Unwrap application-specific error.
  ///
  /// ```
  /// use std::fmt;
  /// use apperr::{AppErr, Blessed};
  ///
  /// #[derive(Debug)]
  /// enum MyErr {
  ///   Something(String)
  /// }
  /// impl fmt::Display for MyErr {
  ///   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  ///     match self {
  ///       MyErr::Something(s) => {
  ///         write!(f, "Some error; {}", s)
  ///       }
  ///     }
  ///   }
  /// }
  /// impl std::error::Error for MyErr { }
  /// impl Blessed for MyErr { }
  ///
  /// let apperr = AppErr::new(MyErr::Something("hello".into()));
  ///
  /// let MyErr::Something(e) = apperr.unwrap_inner::<MyErr>() else {
  ///   panic!("Unexpectedly not MyErr::Something");
  /// };
  /// assert_eq!(e, "hello");
  /// ```
  ///
  /// # Panic
  /// Panics if the inner type is not castable to `E`.
  pub fn unwrap_inner<E: 'static>(self) -> E {
    let Ok(e) = self.0.downcast::<E>() else {
      panic!("Unable to downcast to error type E");
    };
    *e
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :