#![allow(async_fn_in_trait)]
use core::future::IntoFuture;
use core::marker::PhantomData;
use core::num::NonZeroUsize;
use core::time::Duration;
use anyhow::anyhow;
use async_nats::HeaderMap;
use async_nats::jetstream::{self, AckKind, consumer::PullConsumer};
use futures::StreamExt;
use tracing::warn;
use web_time::{SystemTime, UNIX_EPOCH};
use crate::bus::adapter::{
AckHandle, Consumer, Delivery, PublishError, PublishOutcome, Publisher, Source,
};
use crate::bus::{Wire, inbound};
use crate::protocol::ids::headers::{msg_id_of, stamp_msg_id};
type PullMessages = jetstream::consumer::pull::Stream;
pub struct JetStreamAck {
message: jetstream::Message,
sequence: u64,
deliveries: u32,
}
impl AckHandle for JetStreamAck {
async fn ack(self) -> anyhow::Result<()> {
self.message
.ack()
.await
.map_err(|err| anyhow!("jetstream ack failed: {err}"))
}
async fn nak(self, delay: Option<Duration>) -> anyhow::Result<()> {
self.message
.ack_with(AckKind::Nak(delay))
.await
.map_err(|err| anyhow!("jetstream nak failed: {err}"))
}
fn sequence(&self) -> u64 {
self.sequence
}
fn deliveries(&self) -> u32 {
self.deliveries
}
}
async fn build_delivery<T: Wire>(message: jetstream::Message) -> Option<Delivery<T, JetStreamAck>> {
let subject = message.subject.to_string();
let headers = message.headers.clone();
let _ = inbound(&subject, headers.as_ref());
let msg_id = headers
.as_ref()
.and_then(|headers| msg_id_of(headers).map(str::to_owned));
let (sequence, deliveries, sent_at) = match message.info() {
Ok(info) => (
info.stream_sequence,
info.delivered.max(0) as u32,
jetstream_published_at(info.published.unix_timestamp_nanos()),
),
Err(err) => {
warn!("acking message with unreadable JetStream info on {subject}: {err}");
if let Err(ack_err) = message.ack().await {
warn!("could not acknowledge unreadable JetStream message on {subject}: {ack_err}");
}
return None;
}
};
let item = match T::decode(message.payload.as_ref()) {
Ok(item) => item,
Err(err) => {
warn!("acking undecodable message on {subject}: {err}");
if let Err(ack_err) = message.ack().await {
warn!(
"could not acknowledge undecodable JetStream message on {subject}: {ack_err}"
);
}
return None;
}
};
Some(Delivery {
item,
subject,
msg_id,
headers: headers.unwrap_or_default(),
sent_at,
redelivered: deliveries > 1,
handle: JetStreamAck {
message,
sequence,
deliveries,
},
})
}
fn jetstream_published_at(unix_nanos: i128) -> Option<SystemTime> {
u64::try_from(unix_nanos)
.ok()
.map(|nanos| UNIX_EPOCH + Duration::from_nanos(nanos))
}
pub struct JetStreamPublisher<T> {
context: jetstream::Context,
ack_timeout: Duration,
_marker: PhantomData<fn() -> T>,
}
impl<T> JetStreamPublisher<T> {
#[must_use]
pub fn new(context: jetstream::Context, ack_timeout: Duration) -> Self {
Self {
context,
ack_timeout,
_marker: PhantomData,
}
}
}
impl<T> Clone for JetStreamPublisher<T> {
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
ack_timeout: self.ack_timeout,
_marker: PhantomData,
}
}
}
impl<T: Wire + Send + Sync + 'static> Publisher<T> for JetStreamPublisher<T> {
async fn publish_bytes(
&self,
subject: &str,
msg_id: &str,
mut headers: HeaderMap,
bytes: &[u8],
) -> Result<PublishOutcome, PublishError> {
stamp_msg_id(&mut headers, msg_id);
let operation = async {
let pending = self
.context
.publish_with_headers(subject.to_owned(), headers, bytes.to_vec().into())
.await
.map_err(|err| PublishError::Failed(anyhow!("jetstream publish failed: {err}")))?;
pending
.into_future()
.await
.map_err(|err| PublishError::Failed(anyhow!("jetstream publish ack failed: {err}")))
};
match tokio::time::timeout(self.ack_timeout, operation).await {
Err(_elapsed) => Err(PublishError::Ambiguous(anyhow!(
"publish send or acknowledgement timed out after {:?}",
self.ack_timeout
))),
Ok(Err(err)) => Err(err),
Ok(Ok(ack)) => Ok(PublishOutcome::Acked {
sequence: ack.sequence,
duplicate: ack.duplicate,
}),
}
}
}
pub struct JetStreamConsumer<T> {
consumer: PullConsumer,
_marker: PhantomData<fn() -> T>,
}
impl<T> JetStreamConsumer<T> {
#[must_use]
pub fn new(consumer: PullConsumer) -> Self {
Self {
consumer,
_marker: PhantomData,
}
}
}
impl<T: Wire + Send> Consumer<T> for JetStreamConsumer<T> {
type Handle = JetStreamAck;
async fn fetch(
&mut self,
max: usize,
wait: Duration,
) -> anyhow::Result<Vec<Delivery<T, Self::Handle>>> {
if max == 0 {
return Ok(Vec::new());
}
let mut batch = self
.consumer
.fetch()
.max_messages(max)
.expires(wait)
.messages()
.await?;
let mut deliveries = Vec::with_capacity(max);
while let Some(message) = batch.next().await {
match message {
Ok(message) => {
if let Some(delivery) = build_delivery::<T>(message).await {
deliveries.push(delivery);
}
}
Err(err) => {
warn!("jetstream fetch batch error: {err}");
break;
}
}
}
Ok(deliveries)
}
}
pub struct JetStreamSource<T> {
messages: PullMessages,
_marker: PhantomData<fn() -> T>,
}
impl<T> JetStreamSource<T> {
#[must_use]
pub fn new(stream: PullMessages) -> Self {
Self {
messages: stream,
_marker: PhantomData,
}
}
pub async fn from_consumer(
consumer: &PullConsumer,
max_messages_per_batch: NonZeroUsize,
) -> anyhow::Result<Self> {
let messages = consumer
.stream()
.max_messages_per_batch(max_messages_per_batch.get())
.messages()
.await?;
Ok(Self::new(messages))
}
}
impl<T: Wire + Send> Source<T> for JetStreamSource<T> {
type Handle = JetStreamAck;
async fn next(&mut self) -> Option<anyhow::Result<Delivery<T, Self::Handle>>> {
loop {
match self.messages.next().await {
Some(Ok(message)) => match build_delivery::<T>(message).await {
None => continue,
Some(delivery) => return Some(Ok(delivery)),
},
Some(Err(err)) => return Some(Err(anyhow!("jetstream source error: {err}"))),
None => return None,
}
}
}
}