reliar-core 0.4.1

Pure envelope/message model shared by every Reliar crate — no storage or transport dependency.
Documentation
//! Publication (ADR 0008, ADR 0032).

use crate::SerializedEnvelope;
use crate::failure::Classify;

/// The wire side of the outbox. One provider implements this per transport.
///
/// A publish **timeout** classifies as [`crate::FailureKind::Transient`]. A payload the broker
/// rejects as too large classifies as [`crate::FailureKind::Permanent`] — retrying forever
/// cannot help.
///
/// ```
/// use core::fmt;
///
/// use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
///
/// /// A toy publisher that always succeeds — enough to satisfy the trait's bounds.
/// struct NoopPublisher;
///
/// #[derive(Debug)]
/// struct NoopError;
/// impl fmt::Display for NoopError {
///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///         f.write_str("unreachable")
///     }
/// }
/// impl std::error::Error for NoopError {}
/// impl Classify for NoopError {
///     fn kind(&self) -> FailureKind {
///         FailureKind::Permanent
///     }
/// }
///
/// impl Publisher for NoopPublisher {
///     type Error = NoopError;
///
///     fn publish(
///         &self,
///         _envelope: &SerializedEnvelope,
///     ) -> impl Future<Output = Result<(), Self::Error>> + Send {
///         async { Ok(()) }
///     }
/// }
/// ```
pub trait Publisher: Send + Sync {
    /// The error a publish attempt can fail with. Must self-classify via [`Classify`] so the
    /// dispatcher can decide retry vs. dead without inspecting transport internals.
    type Error: std::error::Error + Send + Sync + 'static + Classify;

    /// Publishes one envelope. Never retried by the publisher itself — retry is the
    /// dispatcher's and `RetryPolicy`'s job (`reliar-outbox`).
    ///
    /// ```
    /// # use core::fmt;
    /// # use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
    /// # struct NoopPublisher;
    /// # #[derive(Debug)]
    /// # struct NoopError;
    /// # impl fmt::Display for NoopError {
    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("unreachable") }
    /// # }
    /// # impl std::error::Error for NoopError {}
    /// # impl Classify for NoopError {
    /// #     fn kind(&self) -> FailureKind { FailureKind::Permanent }
    /// # }
    /// # impl Publisher for NoopPublisher {
    /// #     type Error = NoopError;
    /// #     fn publish(&self, _envelope: &SerializedEnvelope) -> impl Future<Output = Result<(), Self::Error>> + Send {
    /// #         async { Ok(()) }
    /// #     }
    /// # }
    /// # #[derive(serde::Serialize, serde::Deserialize)]
    /// # struct Ping;
    /// # impl reliar_core::Message for Ping { const TYPE: &'static str = "ping"; const VERSION: u16 = 1; }
    /// # #[tokio::main]
    /// # async fn main() {
    /// let envelope = reliar_core::Envelope::builder(Ping)
    ///     .build()
    ///     .map_body(|_| bytes::Bytes::from_static(b"{}"));
    /// assert!(NoopPublisher.publish(&envelope).await.is_ok());
    /// # }
    /// ```
    fn publish(
        &self,
        envelope: &SerializedEnvelope,
    ) -> impl Future<Output = Result<(), Self::Error>> + Send;

    /// Publishes a batch. Results are **positional** — one per envelope, in the same order, so
    /// a partial batch failure never loses a per-message verdict.
    ///
    /// The default loops over [`Self::publish`]; a transport with a native batch API overrides
    /// it and owns proving its positional results. **v0.1's dispatcher calls [`Self::publish`],
    /// not this method** — it needs a per-message outcome and a per-message timeout.
    ///
    /// ```
    /// # use core::fmt;
    /// # use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
    /// # struct NoopPublisher;
    /// # #[derive(Debug)]
    /// # struct NoopError;
    /// # impl fmt::Display for NoopError {
    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("unreachable") }
    /// # }
    /// # impl std::error::Error for NoopError {}
    /// # impl Classify for NoopError {
    /// #     fn kind(&self) -> FailureKind { FailureKind::Permanent }
    /// # }
    /// # impl Publisher for NoopPublisher {
    /// #     type Error = NoopError;
    /// #     fn publish(&self, _envelope: &SerializedEnvelope) -> impl Future<Output = Result<(), Self::Error>> + Send {
    /// #         async { Ok(()) }
    /// #     }
    /// # }
    /// # #[derive(serde::Serialize, serde::Deserialize)]
    /// # struct Ping;
    /// # impl reliar_core::Message for Ping { const TYPE: &'static str = "ping"; const VERSION: u16 = 1; }
    /// # #[tokio::main]
    /// # async fn main() {
    /// let envelope = reliar_core::Envelope::builder(Ping)
    ///     .build()
    ///     .map_body(|_| bytes::Bytes::from_static(b"{}"));
    /// let results = NoopPublisher.publish_batch(&[envelope]).await;
    /// assert!(results[0].is_ok());
    /// # }
    /// ```
    fn publish_batch(
        &self,
        envelopes: &[SerializedEnvelope],
    ) -> impl Future<Output = Vec<Result<(), Self::Error>>> + Send {
        async move {
            let mut out = Vec::with_capacity(envelopes.len());

            for envelope in envelopes {
                out.push(self.publish(envelope).await);
            }

            out
        }
    }
}