use std::future::Future;
use nasaga_backend::{SagaBackend, SagaTransactionRunError, SagaTransactionRunner};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SagaTransactionError {
RollbackOnly,
CommitRejected,
CommitUncertain,
RollbackFailed,
Infrastructure,
}
impl SagaTransactionError {
pub fn requires_unbounded_redelivery(self) -> bool {
matches!(
self,
Self::CommitRejected | Self::CommitUncertain | Self::RollbackFailed
)
}
}
impl std::fmt::Display for SagaTransactionError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::RollbackOnly => "Saga transaction rollback-only",
Self::CommitRejected => "Saga transaction commit rejected",
Self::CommitUncertain => "Saga transaction commit uncertain",
Self::RollbackFailed => "Saga transaction rollback failed",
Self::Infrastructure => "Saga transaction infrastructure failed",
})
}
}
impl std::error::Error for SagaTransactionError {}
pub(crate) async fn run_for<B, T, F>(backend: &B, body: F) -> anyhow::Result<T>
where
B: SagaBackend,
F: Future<Output = anyhow::Result<T>> + Send,
T: Send,
{
backend
.transaction_runner()
.run(Box::pin(body))
.await
.map_err(map_error)
}
pub(crate) async fn run_authorized_for<B, T, F>(
backend: &B,
authorize: &(dyn Fn() -> anyhow::Result<()> + Send + Sync),
body: F,
) -> anyhow::Result<T>
where
B: SagaBackend,
F: Future<Output = anyhow::Result<T>> + Send,
T: Send,
{
run_for(backend, async {
let outcome = {
let mut body = Box::pin(body);
std::future::poll_fn(|context| {
if let Err(error) = authorize() {
return std::task::Poll::Ready(Err(error));
}
body.as_mut().poll(context)
})
.await
}?;
authorize()?;
Ok(outcome)
})
.await
}
fn map_error(error: SagaTransactionRunError<anyhow::Error>) -> anyhow::Error {
match error {
SagaTransactionRunError::Rollback(error) => error,
SagaTransactionRunError::RollbackOnly => SagaTransactionError::RollbackOnly.into(),
SagaTransactionRunError::CommitRejected => SagaTransactionError::CommitRejected.into(),
SagaTransactionRunError::CommitUncertain => SagaTransactionError::CommitUncertain.into(),
SagaTransactionRunError::RollbackFailed => SagaTransactionError::RollbackFailed.into(),
SagaTransactionRunError::Infrastructure => SagaTransactionError::Infrastructure.into(),
}
}