#![cfg_attr(
not(feature = "testing"),
allow(clippy::infallible_destructuring_match)
)]
use std::fmt::{Debug, Formatter};
use std::future::{Future, ready};
#[cfg(not(feature = "testing"))]
use std::sync::Arc;
use async_nats::Client;
use bytes::BytesMut;
use ruststream::{OutgoingMessage, PairError, PublishPolicy, Publisher, Take};
use crate::broker::{ConnectedNatsBroker, Link, NatsConnection};
#[cfg(feature = "testing")]
use crate::in_process::{Origin, PublishMode};
#[cfg(feature = "asyncapi")]
use crate::message::REPLY_ADDRESS_LOCATION;
use crate::{convert::nats_parts, error::NatsError};
use self::sealed::Sealed;
mod sealed {
pub trait Sealed {}
impl Sealed for super::NatsPublish {}
impl Sealed for crate::jetstream::JetStreamPublish {}
}
pub trait NatsPublishPolicy: PublishPolicy<ConnectedNatsBroker> + Sealed {
#[must_use]
fn bind(self, connected: &ConnectedNatsBroker) -> Self::Live;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[must_use]
pub struct NatsPublish;
impl PublishPolicy<ConnectedNatsBroker> for NatsPublish {
type Live = NatsPublisher;
fn pair(
self,
connected: &ConnectedNatsBroker,
) -> impl Future<Output = Result<Self::Live, PairError>> {
ready(Ok(self.bind(connected)))
}
#[cfg(feature = "asyncapi")]
fn reply_address_location(&self) -> Option<&'static str> {
Some(REPLY_ADDRESS_LOCATION)
}
}
impl NatsPublishPolicy for NatsPublish {
fn bind(self, connected: &ConnectedNatsBroker) -> Self::Live {
NatsPublisher::new(connected.link().clone())
}
}
#[derive(Clone)]
pub struct NatsPublisher {
link: Link,
}
#[cfg(not(feature = "testing"))]
const _: () = assert!(size_of::<NatsPublisher>() == size_of::<Arc<NatsConnection>>());
impl Debug for NatsPublisher {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NatsPublisher").finish_non_exhaustive()
}
}
impl NatsPublisher {
pub(crate) const fn new(link: Link) -> Self {
Self { link }
}
pub(crate) const fn link(&self) -> &Link {
&self.link
}
}
pub(crate) fn client_for<'a>(
connection: &'a NatsConnection,
subject: &str,
) -> Result<&'a Client, NatsError> {
connection.live_client(subject)
}
impl Publisher for NatsPublisher {
type Payload = Take;
type Error = NatsError;
type Options = ();
async fn publish(
&self,
msg: OutgoingMessage<'_, BytesMut>,
_options: Option<&Self::Options>,
) -> Result<(), Self::Error> {
let connection = match &self.link {
Link::Nats(connection) => connection,
#[cfg(feature = "testing")]
Link::InProcess(bus) => {
let (subject, payload, headers) = msg.into_parts();
return bus
.publish(
subject,
payload.freeze(),
&headers,
None,
PublishMode::Core,
Origin::Connection,
)
.map(drop);
}
};
let client = client_for(connection, msg.name())?;
let (subject, payload, headers) = nats_parts(msg)?;
let result = match headers {
Some(headers) => client.publish_with_headers(subject, headers, payload).await,
None => client.publish(subject, payload).await,
};
result.map_err(|err| NatsError::Publish(Box::new(err)))
}
}