use std::{
fmt::{self, Debug},
future::IntoFuture,
};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use watermelon_proto::{
StatusCode, Subject,
headers::{HeaderMap, HeaderName, HeaderValue, error::HeaderValueValidateError},
};
use crate::{
client::{ClientClosedError, JetstreamClient, JetstreamError},
util::BoxFuture,
};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum JetstreamPublishError {
#[error("jetstream not enabled for this account")]
JetStreamNotEnabled,
#[error("no stream matches the subject")]
NoStreamMatches,
#[error("stream is full")]
StreamFull,
#[error("messages are being discarded")]
MessagesDiscarded,
#[error("jetstream publish error: {0}")]
Other(String),
}
#[derive(Debug, Deserialize)]
pub struct PubAck {
pub stream: String,
#[serde(rename = "seq")]
pub sequence: u64,
#[serde(default)]
pub domain: Option<String>,
#[serde(default)]
pub duplicate: Option<bool>,
}
#[derive(Debug)]
pub struct JetstreamPublish {
subject: Subject,
payload: Bytes,
stream: Option<String>,
expected_stream: Option<String>,
expected_last_stream_sequence: Option<u64>,
expected_last_subject_sequence: Option<u64>,
expected_last_message_id: Option<String>,
message_id: Option<String>,
ttl: Option<u32>,
}
#[derive(Debug)]
pub struct JetstreamPublishBuilder {
publish: JetstreamPublish,
}
pub struct ClientJetstreamPublish<'a> {
client: &'a JetstreamClient,
publish: JetstreamPublish,
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct DoClientJetstreamPublish<'a> {
client: &'a JetstreamClient,
publish: JetstreamPublish,
}
pub struct OwnedClientJetstreamPublish {
client: JetstreamClient,
publish: JetstreamPublish,
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct DoOwnedClientJetstreamPublish {
client: JetstreamClient,
publish: JetstreamPublish,
}
macro_rules! jetstream_publish_builder {
($payload_t:ty) => {
#[must_use]
pub fn stream(mut self, stream: &str) -> Self {
self.publish_mut().stream = Some(stream.to_owned());
self
}
#[must_use]
pub fn expected_stream(mut self, stream: &str) -> Self {
self.publish_mut().expected_stream = Some(stream.to_owned());
self
}
#[must_use]
pub fn expected_last_stream_sequence(mut self, sequence: u64) -> Self {
self.publish_mut().expected_last_stream_sequence = Some(sequence);
self
}
#[must_use]
pub fn expected_last_subject_sequence(mut self, sequence: u64) -> Self {
self.publish_mut().expected_last_subject_sequence = Some(sequence);
self
}
#[must_use]
pub fn expected_last_message_id(mut self, id: &str) -> Self {
self.publish_mut().expected_last_message_id = Some(id.to_owned());
self
}
#[must_use]
pub fn message_id(mut self, id: &str) -> Self {
self.publish_mut().message_id = Some(id.to_owned());
self
}
#[must_use]
pub fn ttl(mut self, seconds: u32) -> Self {
self.publish_mut().ttl = Some(seconds);
self
}
pub fn payload_json<T: Serialize>(
self,
payload: &T,
) -> Result<$payload_t, serde_json::Error> {
let payload = serde_json::to_vec(payload)?;
Ok(self.payload(Bytes::from(payload)))
}
};
}
impl JetstreamPublish {
#[must_use]
pub fn builder(subject: Subject) -> JetstreamPublishBuilder {
JetstreamPublishBuilder::subject(subject)
}
pub fn client(self, client: &JetstreamClient) -> DoClientJetstreamPublish<'_> {
DoClientJetstreamPublish {
client,
publish: self,
}
}
pub fn client_owned(self, client: JetstreamClient) -> DoOwnedClientJetstreamPublish {
DoOwnedClientJetstreamPublish {
client,
publish: self,
}
}
}
impl JetstreamPublishBuilder {
#[must_use]
pub fn subject(subject: Subject) -> Self {
Self {
publish: JetstreamPublish {
subject,
payload: Bytes::new(),
stream: None,
expected_stream: None,
expected_last_stream_sequence: None,
expected_last_subject_sequence: None,
expected_last_message_id: None,
message_id: None,
ttl: None,
},
}
}
jetstream_publish_builder!(JetstreamPublish);
#[must_use]
pub fn payload(mut self, payload: Bytes) -> JetstreamPublish {
self.publish.payload = payload;
self.publish
}
fn publish_mut(&mut self) -> &mut JetstreamPublish {
&mut self.publish
}
}
impl<'a> ClientJetstreamPublish<'a> {
pub(crate) fn build(client: &'a JetstreamClient, subject: Subject) -> Self {
Self {
client,
publish: JetstreamPublishBuilder::subject(subject).publish,
}
}
jetstream_publish_builder!(DoClientJetstreamPublish<'a>);
pub fn payload(mut self, payload: Bytes) -> DoClientJetstreamPublish<'a> {
self.publish.payload = payload;
self.publish.client(self.client)
}
#[must_use]
pub fn to_owned(self) -> OwnedClientJetstreamPublish {
OwnedClientJetstreamPublish {
client: self.client.clone(),
publish: self.publish,
}
}
fn publish_mut(&mut self) -> &mut JetstreamPublish {
&mut self.publish
}
}
impl OwnedClientJetstreamPublish {
pub(crate) fn build(client: JetstreamClient, subject: Subject) -> Self {
Self {
client,
publish: JetstreamPublishBuilder::subject(subject).publish,
}
}
jetstream_publish_builder!(DoOwnedClientJetstreamPublish);
pub fn payload(mut self, payload: Bytes) -> DoOwnedClientJetstreamPublish {
self.publish.payload = payload;
self.publish.client_owned(self.client)
}
fn publish_mut(&mut self) -> &mut JetstreamPublish {
&mut self.publish
}
}
impl DoClientJetstreamPublish<'_> {
pub async fn publish(self) -> Result<PubAck, JetstreamError> {
do_publish(self.client, self.publish).await
}
}
impl<'a> IntoFuture for DoClientJetstreamPublish<'a> {
type Output = Result<PubAck, JetstreamError>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { do_publish(self.client, self.publish).await })
}
}
impl DoOwnedClientJetstreamPublish {
pub async fn publish(self) -> Result<PubAck, JetstreamError> {
do_publish(&self.client, self.publish).await
}
}
impl IntoFuture for DoOwnedClientJetstreamPublish {
type Output = Result<PubAck, JetstreamError>;
type IntoFuture = BoxFuture<'static, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { do_publish(&self.client, self.publish).await })
}
}
pub(crate) async fn do_publish(
client: &JetstreamClient,
jetstream_publish: JetstreamPublish,
) -> Result<PubAck, JetstreamError> {
let JetstreamPublish {
subject,
payload,
stream,
expected_stream,
expected_last_stream_sequence,
expected_last_subject_sequence,
expected_last_message_id,
message_id,
ttl,
} = jetstream_publish;
let headers = build_headers(
stream.as_deref(),
expected_stream.as_deref(),
expected_last_stream_sequence,
expected_last_subject_sequence,
expected_last_message_id.as_deref(),
message_id.as_deref(),
ttl,
)
.map_err(JetstreamError::HeaderValue)?;
let response_fut = client
.client()
.request(subject)
.headers(headers)
.payload(payload)
.await
.map_err(JetstreamError::ClientClosed)?;
let response = response_fut
.await
.map_err(|_| JetstreamError::ClientClosed(ClientClosedError))?;
if let Some(status) = response.status_code {
if status == StatusCode::NO_RESPONDERS {
return Err(JetstreamError::PublishStatus(
crate::client::jetstream::JetstreamPublishError::JetStreamNotEnabled,
));
}
let status_u16 = u16::from(status);
return Err(match status_u16 {
503 => JetstreamError::PublishStatus(
crate::client::jetstream::JetstreamPublishError::JetStreamNotEnabled,
),
409 => JetstreamError::PublishStatus(
crate::client::jetstream::JetstreamPublishError::NoStreamMatches,
),
_ => {
let detail = String::from_utf8_lossy(&response.base.payload).to_string();
JetstreamError::PublishStatus(
crate::client::jetstream::JetstreamPublishError::Other(detail),
)
}
});
}
let pub_ack =
serde_json::from_slice::<PubAck>(&response.base.payload).map_err(JetstreamError::Json)?;
Ok(pub_ack)
}
pub(crate) fn build_headers(
stream: Option<&str>,
expected_stream: Option<&str>,
expected_last_stream_sequence: Option<u64>,
expected_last_subject_sequence: Option<u64>,
expected_last_message_id: Option<&str>,
message_id: Option<&str>,
ttl: Option<u32>,
) -> Result<HeaderMap, HeaderValueValidateError> {
let mut headers = HeaderMap::new();
if let Some(s) = stream {
headers.insert(
HeaderName::from_static("Nats-Stream"),
HeaderValue::from_bytes(s.as_bytes())?,
);
}
if let Some(s) = expected_stream {
headers.insert(
HeaderName::from_static("Nats-Expected-Stream"),
HeaderValue::from_bytes(s.as_bytes())?,
);
}
if let Some(seq) = expected_last_stream_sequence {
headers.insert(
HeaderName::from_static("Nats-Expected-Last-Sequence"),
HeaderValue::from_dangerous_value(Bytes::from(seq.to_string())),
);
}
if let Some(seq) = expected_last_subject_sequence {
headers.insert(
HeaderName::from_static("Nats-Expected-Last-Subject-Sequence"),
HeaderValue::from_dangerous_value(Bytes::from(seq.to_string())),
);
}
if let Some(id) = expected_last_message_id {
headers.insert(
HeaderName::from_static("Nats-Expected-Last-Message-Id"),
HeaderValue::from_bytes(id.as_bytes())?,
);
}
if let Some(id) = message_id {
headers.insert(
HeaderName::from_static("Nats-Message-Id"),
HeaderValue::from_bytes(id.as_bytes())?,
);
}
if let Some(t) = ttl {
headers.insert(
HeaderName::from_static("Nats-TTL"),
HeaderValue::from_dangerous_value(Bytes::from(t.to_string())),
);
}
Ok(headers)
}
impl Debug for ClientJetstreamPublish<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientJetstreamPublish")
.field("publish", &self.publish)
.finish_non_exhaustive()
}
}
impl Debug for DoClientJetstreamPublish<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DoClientJetstreamPublish")
.field("publish", &self.publish)
.finish_non_exhaustive()
}
}
impl Debug for OwnedClientJetstreamPublish {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedClientJetstreamPublish")
.field("publish", &self.publish)
.finish_non_exhaustive()
}
}
impl Debug for DoOwnedClientJetstreamPublish {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DoOwnedClientJetstreamPublish")
.field("publish", &self.publish)
.finish_non_exhaustive()
}
}