use std::{
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use futures::{Sink, Stream};
use pin_project_lite::pin_project;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
codec::CodecType,
error::{Error, Result},
jetstream::{
consumer::{JetStreamMessage, PullBatch, PullConsumer, PullMessages},
stream::{RetentionPolicy, Stream as JsStream, StreamBuilder},
},
};
pub struct WorkQueue<T, C: CodecType> {
stream: JsStream<C>,
consumer: PullConsumer<T, C>,
subject: String,
context: async_nats::jetstream::Context,
_marker: PhantomData<(T, C)>,
}
impl<T, C: CodecType> WorkQueue<T, C> {
pub fn builder(
context: &crate::jetstream::context::JetStreamContext<C>,
name: &str,
) -> WorkQueueBuilder<T, C> {
WorkQueueBuilder::new(context.inner().clone(), name.to_string())
}
pub fn stream(&self) -> &JsStream<C> {
&self.stream
}
pub fn consumer(&self) -> &PullConsumer<T, C> {
&self.consumer
}
pub fn subject(&self) -> &str {
&self.subject
}
}
impl<T: Serialize, C: CodecType> WorkQueue<T, C> {
pub async fn push(&self, message: &T) -> Result<u64> {
let data = C::encode(message)?;
let ack = self
.context
.publish(self.subject.clone(), data.into())
.await
.map_err(|e| Error::JetStream(e.to_string()))?
.await
.map_err(|e| Error::JetStream(e.to_string()))?;
Ok(ack.sequence)
}
pub fn sink(&self) -> WorkQueueSink<T, C> {
WorkQueueSink::new(self.context.clone(), self.subject.clone())
}
}
impl<T: DeserializeOwned, C: CodecType> WorkQueue<T, C> {
pub async fn pull(&self, batch_size: usize) -> Result<PullBatch<T, C>> {
self.consumer.fetch(batch_size).await
}
pub async fn messages(&self) -> Result<crate::jetstream::consumer::PullMessages<T, C>> {
self.consumer.messages().await
}
pub async fn into_stream(self) -> Result<StreamingWorkQueue<T, C>> {
let messages = self.consumer.messages().await?;
Ok(StreamingWorkQueue {
messages,
context: self.context,
subject: self.subject,
_marker: PhantomData,
})
}
}
pin_project! {
pub struct StreamingWorkQueue<T, C: CodecType> {
#[pin]
messages: PullMessages<T, C>,
context: async_nats::jetstream::Context,
subject: String,
_marker: PhantomData<(T, C)>,
}
}
impl<T: Serialize, C: CodecType> StreamingWorkQueue<T, C> {
pub async fn push(&self, message: &T) -> Result<u64> {
let data = C::encode(message)?;
let ack = self
.context
.publish(self.subject.clone(), data.into())
.await
.map_err(|e| Error::JetStream(e.to_string()))?
.await
.map_err(|e| Error::JetStream(e.to_string()))?;
Ok(ack.sequence)
}
}
impl<T: DeserializeOwned, C: CodecType> Stream for StreamingWorkQueue<T, C> {
type Item = Result<JetStreamMessage<T>>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().messages.poll_next(cx)
}
}
pub struct WorkQueueBuilder<T, C: CodecType> {
context: async_nats::jetstream::Context,
name: String,
subject: Option<String>,
max_messages: i64,
max_bytes: i64,
max_age: std::time::Duration,
replicas: usize,
_marker: PhantomData<(T, C)>,
}
impl<T, C: CodecType> WorkQueueBuilder<T, C> {
fn new(context: async_nats::jetstream::Context, name: String) -> Self {
Self {
context,
name,
subject: None,
max_messages: -1,
max_bytes: -1,
max_age: std::time::Duration::ZERO,
replicas: 1,
_marker: PhantomData,
}
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn max_messages(mut self, max: i64) -> Self {
self.max_messages = max;
self
}
pub fn max_bytes(mut self, max: i64) -> Self {
self.max_bytes = max;
self
}
pub fn max_age(mut self, age: std::time::Duration) -> Self {
self.max_age = age;
self
}
pub fn replicas(mut self, replicas: usize) -> Self {
self.replicas = replicas;
self
}
pub async fn create(self) -> Result<WorkQueue<T, C>> {
let subject = self.subject.unwrap_or_else(|| self.name.clone());
let consumer_name = format!("{}-worker", self.name);
let stream_builder = StreamBuilder::<C>::new(self.context.clone(), self.name.clone())
.subjects(vec![subject.clone()])
.retention(RetentionPolicy::WorkQueue)
.max_messages(self.max_messages)
.max_bytes(self.max_bytes)
.max_age(self.max_age)
.replicas(self.replicas);
let stream = stream_builder.create_or_update().await?;
let consumer = stream
.pull_consumer_builder::<T>(&consumer_name)
.durable()
.create_or_update()
.await?;
Ok(WorkQueue {
stream,
consumer,
subject,
context: self.context,
_marker: PhantomData,
})
}
}
pub struct WorkQueueSink<T, C: CodecType> {
context: async_nats::jetstream::Context,
subject: String,
_marker: PhantomData<(T, C)>,
}
impl<T, C: CodecType> WorkQueueSink<T, C> {
fn new(context: async_nats::jetstream::Context, subject: String) -> Self {
Self {
context,
subject,
_marker: PhantomData,
}
}
}
impl<T: Serialize, C: CodecType> WorkQueueSink<T, C> {
pub async fn publish(&self, message: &T) -> Result<u64> {
let data = C::encode(message)?;
let ack = self
.context
.publish(self.subject.clone(), data.into())
.await
.map_err(|e| Error::JetStream(e.to_string()))?
.await
.map_err(|e| Error::JetStream(e.to_string()))?;
Ok(ack.sequence)
}
}
impl<T, C: CodecType> Clone for WorkQueueSink<T, C> {
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
subject: self.subject.clone(),
_marker: PhantomData,
}
}
}
impl<T: Serialize, C: CodecType> Sink<T> for WorkQueueSink<T, C> {
type Error = Error;
fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: T) -> Result<()> {
let data = C::encode(&item)?;
let context = self.context.clone();
let subject = self.subject.clone();
tokio::spawn(async move {
let _ = context.publish(subject, data.into()).await;
});
Ok(())
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
self.poll_flush(cx)
}
}
pub struct InterestQueue<T, C: CodecType> {
stream: JsStream<C>,
subject: String,
context: async_nats::jetstream::Context,
_marker: PhantomData<(T, C)>,
}
impl<T, C: CodecType> InterestQueue<T, C> {
pub fn builder(
context: &crate::jetstream::context::JetStreamContext<C>,
name: &str,
) -> InterestQueueBuilder<T, C> {
InterestQueueBuilder::new(context.inner().clone(), name.to_string())
}
pub fn stream(&self) -> &JsStream<C> {
&self.stream
}
pub fn subject(&self) -> &str {
&self.subject
}
pub async fn add_consumer(&self, name: &str) -> Result<PullConsumer<T, C>> {
self.stream
.pull_consumer_builder::<T>(name)
.durable()
.create_or_update()
.await
}
pub async fn add_consumer_filtered(
&self,
name: &str,
filter: &str,
) -> Result<PullConsumer<T, C>> {
self.stream
.pull_consumer_builder::<T>(name)
.durable()
.filter_subject(filter)
.create_or_update()
.await
}
}
impl<T: Serialize, C: CodecType> InterestQueue<T, C> {
pub async fn publish(&self, message: &T) -> Result<u64> {
let data = C::encode(message)?;
let ack = self
.context
.publish(self.subject.clone(), data.into())
.await
.map_err(|e| Error::JetStream(e.to_string()))?
.await
.map_err(|e| Error::JetStream(e.to_string()))?;
Ok(ack.sequence)
}
pub async fn publish_to(&self, subject: &str, message: &T) -> Result<u64> {
let data = C::encode(message)?;
let ack = self
.context
.publish(subject.to_string(), data.into())
.await
.map_err(|e| Error::JetStream(e.to_string()))?
.await
.map_err(|e| Error::JetStream(e.to_string()))?;
Ok(ack.sequence)
}
}
pub struct InterestQueueBuilder<T, C: CodecType> {
context: async_nats::jetstream::Context,
name: String,
subject: Option<String>,
max_messages: i64,
max_bytes: i64,
max_age: std::time::Duration,
replicas: usize,
_marker: PhantomData<(T, C)>,
}
impl<T, C: CodecType> InterestQueueBuilder<T, C> {
fn new(context: async_nats::jetstream::Context, name: String) -> Self {
Self {
context,
name,
subject: None,
max_messages: -1,
max_bytes: -1,
max_age: std::time::Duration::ZERO,
replicas: 1,
_marker: PhantomData,
}
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn max_messages(mut self, max: i64) -> Self {
self.max_messages = max;
self
}
pub fn max_bytes(mut self, max: i64) -> Self {
self.max_bytes = max;
self
}
pub fn max_age(mut self, age: std::time::Duration) -> Self {
self.max_age = age;
self
}
pub fn replicas(mut self, replicas: usize) -> Self {
self.replicas = replicas;
self
}
pub async fn create(self) -> Result<InterestQueue<T, C>> {
let subject = self.subject.unwrap_or_else(|| self.name.clone());
let stream_builder = StreamBuilder::<C>::new(self.context.clone(), self.name.clone())
.subjects(vec![subject.clone()])
.retention(RetentionPolicy::Interest)
.max_messages(self.max_messages)
.max_bytes(self.max_bytes)
.max_age(self.max_age)
.replicas(self.replicas);
let stream = stream_builder.create_or_update().await?;
Ok(InterestQueue {
stream,
subject,
context: self.context,
_marker: PhantomData,
})
}
}
pub type QueueMessage<T> = JetStreamMessage<T>;