reliar_core/publisher.rs
1//! Publication (ADR 0008, ADR 0032).
2
3use crate::SerializedEnvelope;
4use crate::failure::Classify;
5
6/// The wire side of the outbox. One provider implements this per transport.
7///
8/// A publish **timeout** classifies as [`crate::FailureKind::Transient`]. A payload the broker
9/// rejects as too large classifies as [`crate::FailureKind::Permanent`] — retrying forever
10/// cannot help.
11///
12/// ```
13/// use core::fmt;
14///
15/// use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
16///
17/// /// A toy publisher that always succeeds — enough to satisfy the trait's bounds.
18/// struct NoopPublisher;
19///
20/// #[derive(Debug)]
21/// struct NoopError;
22/// impl fmt::Display for NoopError {
23/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24/// f.write_str("unreachable")
25/// }
26/// }
27/// impl std::error::Error for NoopError {}
28/// impl Classify for NoopError {
29/// fn kind(&self) -> FailureKind {
30/// FailureKind::Permanent
31/// }
32/// }
33///
34/// impl Publisher for NoopPublisher {
35/// type Error = NoopError;
36///
37/// fn publish(
38/// &self,
39/// _envelope: &SerializedEnvelope,
40/// ) -> impl Future<Output = Result<(), Self::Error>> + Send {
41/// async { Ok(()) }
42/// }
43/// }
44/// ```
45pub trait Publisher: Send + Sync {
46 /// The error a publish attempt can fail with. Must self-classify via [`Classify`] so the
47 /// dispatcher can decide retry vs. dead without inspecting transport internals.
48 type Error: std::error::Error + Send + Sync + 'static + Classify;
49
50 /// Publishes one envelope. Never retried by the publisher itself — retry is the
51 /// dispatcher's and `RetryPolicy`'s job (`reliar-outbox`).
52 ///
53 /// ```
54 /// # use core::fmt;
55 /// # use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
56 /// # struct NoopPublisher;
57 /// # #[derive(Debug)]
58 /// # struct NoopError;
59 /// # impl fmt::Display for NoopError {
60 /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("unreachable") }
61 /// # }
62 /// # impl std::error::Error for NoopError {}
63 /// # impl Classify for NoopError {
64 /// # fn kind(&self) -> FailureKind { FailureKind::Permanent }
65 /// # }
66 /// # impl Publisher for NoopPublisher {
67 /// # type Error = NoopError;
68 /// # fn publish(&self, _envelope: &SerializedEnvelope) -> impl Future<Output = Result<(), Self::Error>> + Send {
69 /// # async { Ok(()) }
70 /// # }
71 /// # }
72 /// # #[derive(serde::Serialize, serde::Deserialize)]
73 /// # struct Ping;
74 /// # impl reliar_core::Message for Ping { const TYPE: &'static str = "ping"; const VERSION: u16 = 1; }
75 /// # #[tokio::main]
76 /// # async fn main() {
77 /// let envelope = reliar_core::Envelope::builder(Ping)
78 /// .build()
79 /// .map_body(|_| bytes::Bytes::from_static(b"{}"));
80 /// assert!(NoopPublisher.publish(&envelope).await.is_ok());
81 /// # }
82 /// ```
83 fn publish(
84 &self,
85 envelope: &SerializedEnvelope,
86 ) -> impl Future<Output = Result<(), Self::Error>> + Send;
87
88 /// Publishes a batch. Results are **positional** — one per envelope, in the same order, so
89 /// a partial batch failure never loses a per-message verdict.
90 ///
91 /// The default loops over [`Self::publish`]; a transport with a native batch API overrides
92 /// it and owns proving its positional results. **v0.1's dispatcher calls [`Self::publish`],
93 /// not this method** — it needs a per-message outcome and a per-message timeout.
94 ///
95 /// ```
96 /// # use core::fmt;
97 /// # use reliar_core::{Classify, FailureKind, Publisher, SerializedEnvelope};
98 /// # struct NoopPublisher;
99 /// # #[derive(Debug)]
100 /// # struct NoopError;
101 /// # impl fmt::Display for NoopError {
102 /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("unreachable") }
103 /// # }
104 /// # impl std::error::Error for NoopError {}
105 /// # impl Classify for NoopError {
106 /// # fn kind(&self) -> FailureKind { FailureKind::Permanent }
107 /// # }
108 /// # impl Publisher for NoopPublisher {
109 /// # type Error = NoopError;
110 /// # fn publish(&self, _envelope: &SerializedEnvelope) -> impl Future<Output = Result<(), Self::Error>> + Send {
111 /// # async { Ok(()) }
112 /// # }
113 /// # }
114 /// # #[derive(serde::Serialize, serde::Deserialize)]
115 /// # struct Ping;
116 /// # impl reliar_core::Message for Ping { const TYPE: &'static str = "ping"; const VERSION: u16 = 1; }
117 /// # #[tokio::main]
118 /// # async fn main() {
119 /// let envelope = reliar_core::Envelope::builder(Ping)
120 /// .build()
121 /// .map_body(|_| bytes::Bytes::from_static(b"{}"));
122 /// let results = NoopPublisher.publish_batch(&[envelope]).await;
123 /// assert!(results[0].is_ok());
124 /// # }
125 /// ```
126 fn publish_batch(
127 &self,
128 envelopes: &[SerializedEnvelope],
129 ) -> impl Future<Output = Vec<Result<(), Self::Error>>> + Send {
130 async move {
131 let mut out = Vec::with_capacity(envelopes.len());
132
133 for envelope in envelopes {
134 out.push(self.publish(envelope).await);
135 }
136
137 out
138 }
139 }
140}