use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use async_nats::jetstream;
use async_nats::jetstream::consumer::{PullConsumer, pull::Config as ConsumerConfig};
use async_nats::{Client, ConnectOptions};
use ruststream::{Broker, ConnectedBroker, DefaultPublish, DescribeServer, ServerSpec, Subscribe};
use crate::{
error::NatsError,
publisher::{NatsPublish, NatsPublishPolicy},
subscribe_options::SubscribeOptions,
subscriber::NatsSubscriber,
};
pub(crate) struct NatsConnection {
client: Client,
closed: AtomicBool,
}
impl Debug for NatsConnection {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NatsConnection")
.field("closed", &self.closed.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl NatsConnection {
fn new(client: Client) -> Arc<Self> {
Arc::new(Self {
client,
closed: AtomicBool::new(false),
})
}
pub(crate) fn live_client(&self, subject: &str) -> Result<&Client, NatsError> {
if self.closed.load(Ordering::Acquire) {
return Err(NatsError::Closed {
subject: subject.to_owned(),
});
}
Ok(&self.client)
}
pub(crate) const fn client(&self) -> &Client {
&self.client
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct NatsBroker {
addrs: String,
options: ConnectOptions,
}
impl NatsBroker {
pub fn new(addrs: impl Into<String>) -> Self {
Self {
addrs: addrs.into(),
options: ConnectOptions::default(),
}
}
pub fn with_options(mut self, options: ConnectOptions) -> Self {
self.options = options;
self
}
#[must_use]
pub fn addrs(&self) -> &str {
&self.addrs
}
}
impl Broker for NatsBroker {
type Error = NatsError;
type Connected = ConnectedNatsBroker;
async fn connect(self) -> Result<Self::Connected, Self::Error> {
let client = self
.options
.connect(self.addrs.as_str())
.await
.map_err(|err| NatsError::Connect(Box::new(err)))?;
Ok(ConnectedNatsBroker::from_client(client))
}
}
impl DescribeServer for NatsBroker {
fn describe_server(&self) -> ServerSpec {
let host = self
.addrs
.trim_start_matches("nats://")
.trim_start_matches("tls://")
.to_owned();
ServerSpec::new(host, "nats")
}
}
#[derive(Debug)]
pub struct ConnectedNatsBroker {
connection: Arc<NatsConnection>,
}
impl ConnectedNatsBroker {
#[must_use]
pub fn from_client(client: Client) -> Self {
Self {
connection: NatsConnection::new(client),
}
}
#[must_use]
pub fn publisher<P: NatsPublishPolicy>(&self, policy: P) -> P::Live {
policy.bind(self)
}
#[must_use]
pub fn client(&self) -> Client {
self.connection.client().clone()
}
#[must_use]
pub fn server_spec(&self) -> ServerSpec {
let info = self.connection.client().server_info();
ServerSpec::new(format!("{}:{}", info.host, info.port), "nats")
}
#[must_use]
pub fn jetstream(&self) -> jetstream::Context {
jetstream::new(self.client())
}
pub(crate) fn connection(&self) -> &Arc<NatsConnection> {
&self.connection
}
pub async fn subscribe_with(
&self,
opts: SubscribeOptions,
) -> Result<NatsSubscriber, NatsError> {
opts.validate()?;
if opts.is_jetstream() {
self.subscribe_jetstream(opts).await
} else {
self.subscribe_core(opts).await
}
}
async fn subscribe_core(&self, opts: SubscribeOptions) -> Result<NatsSubscriber, NatsError> {
let client = self.connection.live_client(opts.subject())?;
let subject = opts.subject().to_owned();
let inner = if let Some(queue) = opts.queue_group_ref() {
client
.queue_subscribe(subject.clone(), queue.to_owned())
.await
.map_err(|err| NatsError::Subscribe(Box::new(err)))?
} else {
client
.subscribe(subject.clone())
.await
.map_err(|err| NatsError::Subscribe(Box::new(err)))?
};
client
.flush()
.await
.map_err(|err| NatsError::Subscribe(Box::new(err)))?;
Ok(NatsSubscriber::from_core(subject, inner))
}
async fn subscribe_jetstream(
&self,
opts: SubscribeOptions,
) -> Result<NatsSubscriber, NatsError> {
let client = self.connection.live_client(opts.subject())?.clone();
let ctx = jetstream::new(client);
let stream_name = opts
.stream_ref()
.expect("validated jetstream option")
.to_owned();
let stream = ctx
.get_stream(&stream_name)
.await
.map_err(|err| NatsError::JetStream(Box::new(err)))?;
let consumer_cfg = ConsumerConfig {
durable_name: opts.durable_ref().map(str::to_owned),
filter_subject: opts.filter_subject_or_default(),
max_ack_pending: opts.max_ack_pending_or_default(),
ack_wait: opts.ack_wait_or_default(),
deliver_policy: opts.deliver_policy_or_default(),
..Default::default()
};
let consumer: PullConsumer = stream
.create_consumer(consumer_cfg)
.await
.map_err(|err| NatsError::JetStream(Box::new(err)))?;
let messages = consumer
.messages()
.await
.map_err(|err| NatsError::JetStream(Box::new(err)))?;
Ok(NatsSubscriber::from_jetstream(
opts.subject().to_owned(),
stream_name,
messages,
consumer,
opts.pull_batch_or_default(),
opts.pull_expires_or_default(),
))
}
}
impl ConnectedBroker for ConnectedNatsBroker {
type Error = NatsError;
type Closed = ClosedNatsBroker;
async fn shutdown(self) -> Result<Self::Closed, Self::Error> {
self.connection.closed.store(true, Ordering::Release);
let client = self.connection.client();
let stats = client.statistics();
client
.drain()
.await
.map_err(|err| NatsError::Shutdown(Box::new(err)))?;
Ok(ClosedNatsBroker {
messages_sent: stats.out_messages.load(Ordering::Relaxed),
messages_received: stats.in_messages.load(Ordering::Relaxed),
connects: stats.connects.load(Ordering::Relaxed),
})
}
}
impl Subscribe for ConnectedNatsBroker {
type Subscriber = NatsSubscriber;
async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
self.subscribe_with(SubscribeOptions::new(name)).await
}
}
impl DefaultPublish for ConnectedNatsBroker {
type Policy = NatsPublish;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClosedNatsBroker {
messages_sent: u64,
messages_received: u64,
connects: u64,
}
impl ClosedNatsBroker {
#[must_use]
pub const fn messages_sent(&self) -> u64 {
self.messages_sent
}
#[must_use]
pub const fn messages_received(&self) -> u64 {
self.messages_received
}
#[must_use]
pub const fn connects(&self) -> u64 {
self.connects
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_performs_no_io_and_describes_the_configured_address() {
let broker = NatsBroker::new("nats://127.0.0.1:4222");
let spec = broker.describe_server();
assert_eq!(spec.protocol, "nats");
assert_eq!(spec.host.as_deref(), Some("127.0.0.1:4222"));
}
}