use std::fmt;
use std::marker::PhantomData;
use bytes::BytesMut;
use serde::Serialize;
use thiserror::Error;
use tracing::warn;
use super::{HeadersUnset, MessageBody, PublishBuilder, PublishCodec, message_of};
use crate::codec::CodecError;
use crate::{
OutgoingDestination, OutgoingMessage, OwnedTransactions, PayloadForm, Publisher, Transaction,
TransactionalPublisher,
};
#[doc(hidden)]
pub trait Admits<T, Index> {}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct AnyDeclared;
impl<T> Admits<T, ()> for AnyDeclared {}
#[must_use = "a transaction scope must be settled with commit() or abort()"]
pub struct TransactionScope<'a, P, Enc, Admit> {
publisher: &'a P,
enc: Enc,
open: bool,
_admit: PhantomData<fn() -> Admit>,
}
impl<'a, P, Enc, Admit> TransactionScope<'a, P, Enc, Admit>
where
P: TransactionalPublisher,
{
pub(crate) async fn open(publisher: &'a P, enc: Enc) -> Result<Self, P::Error> {
publisher.begin_transaction().await?;
Ok(Self {
publisher,
enc,
open: true,
_admit: PhantomData,
})
}
}
impl<'s, P: Publisher, Enc: Copy, Admit> TransactionScope<'s, P, Enc, Admit> {
pub fn message<'a, T, Index>(
&'a self,
value: &'a T,
) -> PublishBuilder<&'s P, MessageBody<'a, T>, Enc, HeadersUnset, T::Form>
where
T: OutgoingDestination,
Admit: Admits<T, Index>,
{
message_of(self.publisher, value, self.enc)
}
}
impl<P, Enc, Admit> TransactionScope<'_, P, Enc, Admit>
where
P: TransactionalPublisher,
Enc: PublishCodec,
{
pub async fn publish<T: Serialize + Sync>(
&mut self,
name: &str,
value: &T,
) -> Result<(), TransactionPublishError<P::Error>> {
let mut buf = BytesMut::new();
let slot = <P::Payload as PayloadForm>::encode_slot(&mut buf);
let payload = <P::Payload as PayloadForm>::encoded(self.enc.codec(), value, slot)
.map_err(TransactionPublishError::Encode)?;
self.publisher
.publish(OutgoingMessage::with_payload(name, payload), None)
.await
.map_err(TransactionPublishError::Publish)
}
pub async fn commit(mut self) -> Result<(), P::Error> {
let result = self.publisher.commit().await;
self.open = false;
result
}
pub async fn abort(mut self) -> Result<(), P::Error> {
let result = self.publisher.abort().await;
self.open = false;
result
}
}
impl<P, Enc, Admit> Drop for TransactionScope<'_, P, Enc, Admit> {
fn drop(&mut self) {
if self.open {
warn!(
target: "ruststream::dispatch",
"transaction scope dropped without commit or abort; the broker transaction stays \
open on this publisher handle until it is settled or the handle is dropped"
);
}
}
}
impl<P, Enc, Admit> fmt::Debug for TransactionScope<'_, P, Enc, Admit> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionScope")
.field("open", &self.open)
.finish_non_exhaustive()
}
}
#[must_use = "a transaction does nothing until settled with commit() or abort()"]
pub struct TypedTransaction<Txn, Enc, Admit> {
txn: Txn,
enc: Enc,
_admit: PhantomData<fn() -> Admit>,
}
impl<Txn, Enc, Admit> TypedTransaction<Txn, Enc, Admit> {
pub(crate) async fn open<P>(publisher: &P, enc: Enc) -> Result<Self, P::Error>
where
P: OwnedTransactions<Transaction = Txn>,
{
Ok(Self {
txn: publisher.transaction().await?,
enc,
_admit: PhantomData,
})
}
}
impl<Txn: Transaction, Enc: Copy, Admit> TypedTransaction<Txn, Enc, Admit> {
pub fn message<'a, T, Index>(
&'a mut self,
value: &'a T,
) -> PublishBuilder<&'a mut Txn, MessageBody<'a, T>, Enc, HeadersUnset, T::Form>
where
T: OutgoingDestination,
Admit: Admits<T, Index>,
{
message_of(&mut self.txn, value, self.enc)
}
}
impl<Txn, Enc, Admit> TypedTransaction<Txn, Enc, Admit>
where
Txn: Transaction,
Enc: PublishCodec,
{
pub async fn publish<T>(
&mut self,
name: &str,
value: &T,
) -> Result<(), TransactionPublishError<Txn::Error>>
where
T: Serialize + Sync,
{
let mut buf = BytesMut::new();
let slot = <Txn::Payload as PayloadForm>::encode_slot(&mut buf);
let payload = <Txn::Payload as PayloadForm>::encoded(self.enc.codec(), value, slot)
.map_err(TransactionPublishError::Encode)?;
self.txn
.publish(OutgoingMessage::with_payload(name, payload), None)
.await
.map_err(TransactionPublishError::Publish)
}
pub async fn commit(self) -> Result<(), Txn::Error> {
self.txn.commit().await
}
pub async fn abort(self) -> Result<(), Txn::Error> {
self.txn.abort().await
}
}
impl<Txn, Enc, Admit> fmt::Debug for TypedTransaction<Txn, Enc, Admit> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedTransaction").finish_non_exhaustive()
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TransactionPublishError<E> {
#[error("failed to encode the value for a transactional publish")]
Encode(#[source] CodecError),
#[error("failed to publish inside the transaction")]
Publish(#[source] E),
}