Skip to main content

ruststream_nats/
publisher.rs

1//! Core NATS publishing: the [`NatsPublish`] policy and its live [`NatsPublisher`].
2
3use std::fmt::{Debug, Formatter};
4use std::sync::Arc;
5
6use async_nats::Client;
7use bytes::Bytes;
8use ruststream::{OutgoingMessage, PairError, PublishPolicy, Publisher};
9
10use crate::broker::{ConnectedNatsBroker, NatsConnection};
11use crate::{convert::headers_to_nats, error::NatsError};
12
13use self::sealed::Sealed;
14
15mod sealed {
16    /// Seals [`NatsPublishPolicy`](super::NatsPublishPolicy): pairing a NATS publisher is
17    /// synchronous and infallible for both of this crate's policies, and the synchronous
18    /// [`publisher`](crate::ConnectedNatsBroker::publisher) accessor depends on that.
19    pub trait Sealed {}
20
21    impl Sealed for super::NatsPublish {}
22    impl Sealed for crate::jetstream::JetStreamPublish {}
23}
24
25/// A publish policy that pairs with a connected NATS broker without I/O.
26///
27/// Both NATS policies hold nothing but publish options, so bringing one alive is a constructor
28/// call rather than broker work. That is what lets
29/// [`ConnectedNatsBroker::publisher`](crate::ConnectedNatsBroker::publisher) be synchronous;
30/// [`PublishPolicy::pair`], the framework-side entry point, delegates here.
31pub trait NatsPublishPolicy: PublishPolicy<ConnectedNatsBroker> + Sealed {
32    /// Pairs the policy with the connected broker, producing the live publisher.
33    #[must_use]
34    fn bind(self, connected: &ConnectedNatsBroker) -> Self::Live;
35}
36
37/// The Core NATS publish policy: pure declaration, constructible anywhere.
38///
39/// Core NATS publishing carries no per-publisher options (subject and headers travel with each
40/// message), so the policy is a unit marker. It pairs into [`NatsPublisher`], which also serves
41/// the [`RequestReply`](ruststream::RequestReply) capability, and it is the broker's
42/// [`DefaultPublish`](ruststream::DefaultPublish) policy, so a `publish("subject")` handler
43/// mounted without an explicit publisher replies through it.
44///
45/// # Examples
46///
47/// ```
48/// use ruststream_nats::NatsPublish;
49///
50/// let policy = NatsPublish;
51/// # let _ = policy;
52/// ```
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
54#[must_use]
55pub struct NatsPublish;
56
57impl PublishPolicy<ConnectedNatsBroker> for NatsPublish {
58    type Live = NatsPublisher;
59
60    async fn pair(self, connected: &ConnectedNatsBroker) -> Result<Self::Live, PairError> {
61        Ok(self.bind(connected))
62    }
63}
64
65impl NatsPublishPolicy for NatsPublish {
66    fn bind(self, connected: &ConnectedNatsBroker) -> Self::Live {
67        NatsPublisher::new(Arc::clone(connected.connection()))
68    }
69}
70
71/// The live Core NATS publisher. Cheap to clone.
72///
73/// Exists only from a [`ConnectedNatsBroker`], so it always has a connection. It aliases that
74/// connection, though, and may outlive it: after the broker shuts down every publish reports
75/// [`NatsError::Closed`] instead of silently succeeding against a dead connection.
76#[derive(Clone)]
77pub struct NatsPublisher {
78    connection: Arc<NatsConnection>,
79}
80
81impl Debug for NatsPublisher {
82    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("NatsPublisher").finish_non_exhaustive()
84    }
85}
86
87impl NatsPublisher {
88    pub(crate) const fn new(connection: Arc<NatsConnection>) -> Self {
89        Self { connection }
90    }
91
92    pub(crate) fn client_for(&self, subject: &str) -> Result<Client, NatsError> {
93        self.connection.live_client(subject).cloned()
94    }
95}
96
97impl Publisher for NatsPublisher {
98    type Error = NatsError;
99
100    /// # Cancel safety
101    ///
102    /// Core NATS publishing is fire-and-forget: the message is handed to the connection's writer
103    /// without waiting for the server. Dropping the future may leave the message either sent or
104    /// unsent, with no way to tell which.
105    async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
106        let client = self.client_for(msg.name())?;
107        let subject = msg.name().to_owned();
108        let payload = Bytes::copy_from_slice(msg.payload());
109        let result = match headers_to_nats(msg.headers()) {
110            Some(headers) => client.publish_with_headers(subject, headers, payload).await,
111            None => client.publish(subject, payload).await,
112        };
113        result.map_err(|err| NatsError::Publish(Box::new(err)))
114    }
115}