rabbit_warren 0.1.2

An ergonomic, production-ready RabbitMQ client built on top of lapin, featuring automatic ack/nack strategies and distributed tracing.
use lapin::types::ShortString;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error("Lapin error")]
    Lapin(#[from] lapin::Error),

    #[error("Serde JSON error")]
    SerdeJson(#[from] serde_json::Error),

    #[error("UTF-8 conversion error")]
    Utf8(#[from] std::str::Utf8Error),

    #[error("Publish acknowledgement error: code {code}, text {text}")]
    PublishAck { code: u16, text: ShortString },
}

pub type Result<T> = std::result::Result<T, Error>;

// ====================
// ACK / NACK STRATEGY
// ====================

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AckAction {
    Ack,
    Requeue,
    Discard,
}

pub trait IntoAckAction {
    fn ack_action(&self) -> AckAction;
}

#[derive(Debug)]
pub struct ProcessingError<E> {
    pub error: E,
    pub action: AckAction,
}

impl<E> ProcessingError<E> {
    pub fn new(error: E, action: AckAction) -> Self {
        Self { error, action }
    }

    pub fn retryable(error: E) -> Self {
        Self {
            error,
            action: AckAction::Requeue,
        }
    }

    pub fn permanent(error: E) -> Self {
        Self {
            error,
            action: AckAction::Discard,
        }
    }
}

impl<E: std::fmt::Display> IntoAckAction for ProcessingError<E> {
    fn ack_action(&self) -> AckAction {
        self.action
    }
}

impl<E: std::fmt::Display> std::fmt::Display for ProcessingError<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.error.fmt(f)
    }
}

pub trait ResultExt<T, E> {
    fn requeue_on_err(self) -> std::result::Result<T, ProcessingError<E>>;
    fn discard_on_err(self) -> std::result::Result<T, ProcessingError<E>>;
    fn with_ack_action(self, action: AckAction) -> std::result::Result<T, ProcessingError<E>>;
    fn into_processing_error(self) -> std::result::Result<T, ProcessingError<E>>
    where
        E: IntoAckAction;
}

impl<T, E> ResultExt<T, E> for std::result::Result<T, E> {
    fn requeue_on_err(self) -> std::result::Result<T, ProcessingError<E>> {
        self.map_err(ProcessingError::retryable)
    }

    fn discard_on_err(self) -> std::result::Result<T, ProcessingError<E>> {
        self.map_err(ProcessingError::permanent)
    }

    fn with_ack_action(self, action: AckAction) -> std::result::Result<T, ProcessingError<E>> {
        self.map_err(|e| ProcessingError::new(e, action))
    }

    fn into_processing_error(self) -> std::result::Result<T, ProcessingError<E>>
    where
        E: IntoAckAction,
    {
        self.map_err(|e| {
            let action = e.ack_action();
            ProcessingError::new(e, action)
        })
    }
}