use crate::{
COUNTER_INCREMENT_HEADER, Entry, Result,
errors::{CounterError, CounterErrorKind},
parser::{
parse_counter_value, parse_counter_value_from_string, parse_increment, parse_sources,
},
};
use async_nats::{
HeaderMap,
jetstream::{self, stream::Stream},
subject::ToSubject,
};
use futures_util::{Stream as FutureStream, TryStreamExt};
use jetstream_extra::batch_fetch::BatchFetchExt;
use num_bigint::BigInt;
use std::pin::Pin;
pub struct Counter {
stream: Stream,
context: jetstream::Context,
}
impl Counter {
pub async fn from_stream(context: jetstream::Context, mut stream: Stream) -> Result<Self> {
let info = stream
.info()
.await
.map_err(|e| CounterError::with_source(CounterErrorKind::Request, e))?;
if !info.config.allow_message_counter {
return Err(CounterError::new(CounterErrorKind::CounterNotEnabled));
}
if !info.config.allow_direct {
return Err(CounterError::new(CounterErrorKind::DirectAccessRequired));
}
Ok(Self { stream, context })
}
pub async fn add<S, V>(&self, subject: S, value: V) -> Result<BigInt>
where
S: ToSubject,
V: Into<BigInt> + Send,
{
let value = value.into();
let mut headers = HeaderMap::new();
headers.insert(COUNTER_INCREMENT_HEADER, value.to_string());
let ack = self
.context
.publish_with_headers(subject, headers, vec![].into())
.await
.map_err(|e| CounterError::with_source(CounterErrorKind::Publish, e))?
.await
.map_err(|e| CounterError::with_source(CounterErrorKind::Publish, e))?;
parse_counter_value_from_string(ack.value)
}
pub async fn get<S: ToSubject>(&self, subject: S) -> Result<Entry> {
let subject_str = subject.to_subject();
let msg = self
.stream
.get_last_raw_message_by_subject(&subject_str)
.await
.map_err(|e| match e.kind() {
jetstream::stream::LastRawMessageErrorKind::NoMessageFound => {
CounterError::new(CounterErrorKind::NoCounterForSubject)
}
_ => CounterError::with_source(CounterErrorKind::Stream, e),
})?;
let value = parse_counter_value(&msg.payload)?;
let sources = parse_sources(&msg.headers)?;
let increment = parse_increment(&msg.headers)?;
Ok(Entry {
subject: subject_str.to_string(),
value,
sources,
increment,
})
}
pub async fn load<S: ToSubject>(&self, subject: S) -> Result<BigInt> {
Ok(self.get(subject).await?.value)
}
pub async fn increment<S>(&self, subject: S) -> Result<BigInt>
where
S: ToSubject,
{
self.add(subject, 1).await
}
pub async fn decrement<S>(&self, subject: S) -> Result<BigInt>
where
S: ToSubject,
{
self.add(subject, -1).await
}
pub async fn get_multiple(
&self,
subjects: Vec<String>,
) -> Result<Pin<Box<dyn FutureStream<Item = Result<Entry>> + Send + '_>>> {
if subjects.is_empty() {
return Err(CounterError::new(CounterErrorKind::InvalidCounterValue));
}
for subject in &subjects {
if subject.is_empty() {
return Err(CounterError::new(CounterErrorKind::InvalidCounterValue));
}
}
let stream_name = self.stream.cached_info().config.name.clone();
let messages = self
.context
.get_last_messages_for(&stream_name)
.subjects(subjects)
.send()
.await
.map_err(|e| CounterError::with_source(CounterErrorKind::Stream, e))?;
let entries = messages
.map_err(|e| CounterError::with_source(CounterErrorKind::Stream, e))
.and_then(|msg| async move {
let value = parse_counter_value(&msg.payload)?;
let sources = parse_sources(&msg.headers)?;
let increment = parse_increment(&msg.headers)?;
Ok(Entry {
subject: msg.subject.to_string(),
value,
sources,
increment,
})
});
Ok(Box::pin(entries)
as Pin<
Box<dyn FutureStream<Item = Result<Entry>> + Send + '_>,
>)
}
}