use std::{error::Error, fmt, sync::Arc};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Category {
Domain,
Transient,
Permanent,
}
impl fmt::Display for Category {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Domain => "domain rule violated",
Self::Transient => "transient failure",
Self::Permanent => "permanent failure",
})
}
}
pub trait Categorized {
fn category(&self) -> Option<Category>;
}
impl<T: Categorized + ?Sized> Categorized for &T {
fn category(&self) -> Option<Category> {
(**self).category()
}
}
impl<T: Categorized + ?Sized> Categorized for Box<T> {
fn category(&self) -> Option<Category> {
(**self).category()
}
}
impl<T: Categorized + ?Sized> Categorized for Arc<T> {
fn category(&self) -> Option<Category> {
(**self).category()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DomainError {
message: String,
}
impl DomainError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for DomainError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl Error for DomainError {}
impl Categorized for DomainError {
fn category(&self) -> Option<Category> {
Some(Category::Domain)
}
}
#[derive(Debug)]
pub struct Failure {
message: String,
cause: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl Failure {
pub(crate) fn new(
message: impl Into<String>,
cause: Option<Box<dyn Error + Send + Sync + 'static>>,
) -> Self {
Self {
message: message.into(),
cause,
}
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn cause(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
self.cause.as_deref()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Transient {
Conflict(Failure),
Other(Failure),
}
impl Transient {
fn failure(&self) -> &Failure {
match self {
Self::Conflict(failure) | Self::Other(failure) => failure,
}
}
}
impl fmt::Display for Transient {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Conflict(failure) => write!(
formatter,
"{}: a precondition did not hold: {}",
Category::Transient,
failure.message,
),
Self::Other(failure) => {
write!(formatter, "{}: {}", Category::Transient, failure.message)
}
}
}
}
impl Error for Transient {
fn source(&self) -> Option<&(dyn Error + 'static)> {
let cause: &(dyn Error + 'static) = self.failure().cause.as_deref()?;
Some(cause)
}
}
impl Categorized for Transient {
fn category(&self) -> Option<Category> {
Some(Category::Transient)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Permanent {
Unverified(Failure),
Other(Failure),
}
impl Permanent {
fn failure(&self) -> &Failure {
match self {
Self::Unverified(failure) | Self::Other(failure) => failure,
}
}
}
impl fmt::Display for Permanent {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unverified(failure) => write!(
formatter,
"{}: an event could not be verified: {}",
Category::Permanent,
failure.message,
),
Self::Other(failure) => {
write!(formatter, "{}: {}", Category::Permanent, failure.message)
}
}
}
}
impl Error for Permanent {
fn source(&self) -> Option<&(dyn Error + 'static)> {
let cause: &(dyn Error + 'static) = self.failure().cause.as_deref()?;
Some(cause)
}
}
impl Categorized for Permanent {
fn category(&self) -> Option<Category> {
Some(Category::Permanent)
}
}
impl Categorized for std::convert::Infallible {
fn category(&self) -> Option<Category> {
match *self {}
}
}