payjoin 1.0.0-rc.6

Payjoin Library implementing BIP 78 and BIP 77 batching protocols.
Documentation
use std::fmt::Debug;
use std::{error, fmt};

#[derive(Debug)]
pub struct ImplementationError(Box<dyn error::Error + Send + Sync>);

impl ImplementationError {
    pub fn new(e: impl error::Error + Send + Sync + 'static) -> Self {
        ImplementationError(Box::new(e))
    }
}

impl fmt::Display for ImplementationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { std::fmt::Display::fmt(&self.0, f) }
}

impl error::Error for ImplementationError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(self.0.as_ref()) }
}

impl PartialEq for ImplementationError {
    fn eq(&self, _: &Self) -> bool { false }
}

impl Eq for ImplementationError {}

impl From<Box<dyn error::Error + Send + Sync>> for ImplementationError {
    fn from(e: Box<dyn error::Error + Send + Sync>) -> Self { ImplementationError(e) }
}

impl From<&str> for ImplementationError {
    fn from(e: &str) -> Self {
        let error = Box::<dyn error::Error + Send + Sync>::from(e);
        ImplementationError::from(error)
    }
}
/// Errors that can occur when replaying a session event log
#[cfg(feature = "v2")]
#[derive(Debug)]
pub struct ReplayError<SessionState, SessionEvent>(InternalReplayError<SessionState, SessionEvent>);

#[cfg(feature = "v2")]
impl<SessionState: Debug, SessionEvent: Debug> std::fmt::Display
    for ReplayError<SessionState, SessionEvent>
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use InternalReplayError::*;
        match &self.0 {
            NoEvents => write!(f, "No events found in session"),
            InvalidEvent(event, session) => match session {
                Some(session) => write!(f, "Invalid event ({event:?}) for session ({session:?})",),
                None => write!(f, "Invalid first event ({event:?}) for session",),
            },
            InvalidEventPayload(event, reason) =>
                write!(f, "Invalid event payload ({event:?}): {reason}"),
            Expired(time, _) => write!(f, "Session expired at {time:?}"),
            PersistenceFailure(e) => write!(f, "Persistence failure: {e}"),
        }
    }
}
#[cfg(feature = "v2")]
impl<SessionState: Debug, SessionEvent: Debug> std::error::Error
    for ReplayError<SessionState, SessionEvent>
{
}

#[cfg(feature = "v2")]
impl<SessionState: Debug, SessionEvent: Debug> From<InternalReplayError<SessionState, SessionEvent>>
    for ReplayError<SessionState, SessionEvent>
{
    fn from(e: InternalReplayError<SessionState, SessionEvent>) -> Self { ReplayError(e) }
}

#[cfg(feature = "v2")]
impl<SessionState, SessionEvent> ReplayError<SessionState, SessionEvent> {
    /// Returns `true` if the event log could not be replayed because the
    /// session has expired.
    pub fn is_expired(&self) -> bool { matches!(self.0, InternalReplayError::Expired(..)) }

    /// Returns the fallback transaction that should be broadcast when the
    /// session has expired, if one is recoverable from the event log.
    ///
    /// Returns `None` for non-expired errors, or for an expired session that
    /// never produced a fallback transaction (e.g. a receiver that expired
    /// before receiving the sender's original proposal).
    pub fn expiry_fallback_tx(&self) -> Option<&bitcoin::Transaction> {
        match &self.0 {
            InternalReplayError::Expired(_, tx) => tx.as_ref(),
            _ => None,
        }
    }
}

#[cfg(feature = "v2")]
#[derive(Debug)]
pub(crate) enum InternalReplayError<SessionState, SessionEvent> {
    /// No events in the event log
    NoEvents,
    /// Invalid initial event
    InvalidEvent(Box<SessionEvent>, Option<Box<SessionState>>),
    /// Event payload failed validation
    InvalidEventPayload(Box<SessionEvent>, String),
    /// Session is expired. Carries the recoverable fallback transaction, if any.
    Expired(crate::time::Time, Option<bitcoin::Transaction>),
    /// Application storage error
    PersistenceFailure(ImplementationError),
}

#[cfg(all(test, feature = "v2"))]
mod tests {
    use super::*;
    use crate::time::Time;

    #[test]
    fn replay_error_is_expired() {
        let expired: ReplayError<(), ()> =
            ReplayError(InternalReplayError::Expired(Time::now(), None));
        assert!(expired.is_expired());
        assert!(expired.expiry_fallback_tx().is_none());

        let other: ReplayError<(), ()> = ReplayError(InternalReplayError::NoEvents);
        assert!(!other.is_expired());
    }
}