reliar-inbox 0.2.0

Transactional inbox deduplication: InboxStore/InboxHandler contracts, claim/complete/fail/purge semantics (no storage or transport dependency).
Documentation
//! [`crate::InboxStore::process`]'s error.

use core::fmt;

use reliar_core::{Classify, FailureKind};

/// Whose fault it was. Split so the caller can tell "my handler failed" (record it, `nak`) from
/// "the inbox could not be read or written" (record nothing, `nak`).
///
/// ```
/// use reliar_inbox::InboxProcessError;
///
/// #[derive(Debug)]
/// struct StoreError;
/// impl std::fmt::Display for StoreError {
///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
///         f.write_str("store unavailable")
///     }
/// }
/// impl std::error::Error for StoreError {}
///
/// let err: InboxProcessError<StoreError, std::convert::Infallible> =
///     InboxProcessError::Store(StoreError);
/// assert_eq!(err.to_string(), "inbox store failed: store unavailable");
/// ```
#[derive(Debug)]
#[non_exhaustive]
pub enum InboxProcessError<S, H> {
    /// [`crate::InboxStore::claim`] or [`crate::InboxStore::complete`] failed.
    Store(S),

    /// The [`crate::InboxHandler`] failed.
    Handler(H),
}

impl<S, H> fmt::Display for InboxProcessError<S, H>
where
    S: std::error::Error,
    H: std::error::Error,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Store(err) => write!(f, "inbox store failed: {err}"),
            Self::Handler(err) => write!(f, "inbox handler failed: {err}"),
        }
    }
}

impl<S, H> std::error::Error for InboxProcessError<S, H>
where
    S: std::error::Error + 'static,
    H: std::error::Error + 'static,
{
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Store(err) => Some(err),
            Self::Handler(err) => Some(err),
        }
    }
}

/// Maps [`Self::Store`] to the store error's own [`FailureKind`] and [`Self::Handler`] to
/// **[`FailureKind::Transient`]** — a handler failure is retried by the broker's redelivery, and
/// the inbox has no authority to call a message permanently dead (ADR 0042 §4).
///
/// ```
/// use reliar_core::{Classify, FailureKind};
/// use reliar_inbox::InboxProcessError;
///
/// #[derive(Debug)]
/// struct StoreError;
/// impl Classify for StoreError {
///     fn kind(&self) -> FailureKind {
///         FailureKind::Permanent
///     }
/// }
///
/// #[derive(Debug)]
/// struct HandlerError;
///
/// // A handler failure is always `Transient`, regardless of the store error's own kind.
/// let err: InboxProcessError<StoreError, HandlerError> = InboxProcessError::Handler(HandlerError);
/// assert_eq!(err.kind(), FailureKind::Transient);
/// ```
impl<S, H> Classify for InboxProcessError<S, H>
where
    S: Classify,
{
    fn kind(&self) -> FailureKind {
        match self {
            Self::Store(err) => err.kind(),
            Self::Handler(_) => FailureKind::Transient,
        }
    }
}