#![allow(clippy::type_complexity)]
use crate::{AggregateRoot, AppendError, AppendResult, AsyncTransactionalOutbox, ExpectedRevision};
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, PartialEq, Eq)]
pub enum AsyncTransactionalOutboxOperationError<E> {
Adapter(AppendError<E>),
AlreadyEnqueued,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsyncTransactionalOutboxCompletionError {
MissingEnqueue,
EnqueueFailedConsumed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OperationState {
Pending,
Succeeded,
FailedConsumed,
}
pub struct AsyncTransactionalOutboxLifecycleContext<'storage, A, O>
where
A: AggregateRoot + Send + Sync,
A::Event: Send,
O: AsyncTransactionalOutbox<Aggregate = A> + Send,
O::Error: Send,
{
aggregate: &'storage mut A,
outbox: &'storage mut O,
expected: ExpectedRevision,
state: OperationState,
}
impl<'storage, A, O> AsyncTransactionalOutboxLifecycleContext<'storage, A, O>
where
A: AggregateRoot + Send + Sync,
A::Event: Send,
O: AsyncTransactionalOutbox<Aggregate = A> + Send,
O::Error: Send,
{
pub fn new(
aggregate: &'storage mut A,
outbox: &'storage mut O,
expected: ExpectedRevision,
) -> Self {
Self {
aggregate,
outbox,
expected,
state: OperationState::Pending,
}
}
pub fn save_and_enqueue<'ctx>(
&'ctx mut self,
) -> Pin<
Box<
dyn Future<
Output = Result<AppendResult, AsyncTransactionalOutboxOperationError<O::Error>>,
> + Send
+ 'ctx,
>,
>
where
'storage: 'ctx,
A: 'ctx,
A::Event: 'ctx,
O: 'ctx,
O::Error: 'ctx,
{
if self.state != OperationState::Pending {
return Box::pin(async {
Err(AsyncTransactionalOutboxOperationError::AlreadyEnqueued)
});
}
self.state = OperationState::FailedConsumed;
let state = &mut self.state;
let events = self.aggregate.pull_events();
let aggregate: &'ctx A = &*self.aggregate;
let outbox: &'ctx mut O = &mut *self.outbox;
let expected = self.expected;
Box::pin(async move {
let mut guard = AttemptGuard::new(state);
match outbox.save_and_enqueue(aggregate, expected, events).await {
Ok(result) => {
guard.succeed();
Ok(result)
}
Err(error) => Err(AsyncTransactionalOutboxOperationError::Adapter(error)),
}
})
}
pub fn complete(self) -> Result<(), AsyncTransactionalOutboxCompletionError> {
match self.state {
OperationState::Succeeded => Ok(()),
OperationState::Pending => Err(AsyncTransactionalOutboxCompletionError::MissingEnqueue),
OperationState::FailedConsumed => {
Err(AsyncTransactionalOutboxCompletionError::EnqueueFailedConsumed)
}
}
}
}
struct AttemptGuard<'a> {
state: &'a mut OperationState,
succeeded: bool,
}
impl<'a> AttemptGuard<'a> {
fn new(state: &'a mut OperationState) -> Self {
Self {
state,
succeeded: false,
}
}
fn succeed(&mut self) {
*self.state = OperationState::Succeeded;
self.succeeded = true;
}
}
impl Drop for AttemptGuard<'_> {
fn drop(&mut self) {
if !self.succeeded {
*self.state = OperationState::FailedConsumed;
}
}
}
pub type AsyncOutboxLifecycleContext<'a, A, O> = AsyncTransactionalOutboxLifecycleContext<'a, A, O>;
pub type AsyncOutboxLifecycleOperationError<E> = AsyncTransactionalOutboxOperationError<E>;
pub type AsyncOutboxLifecycleCompletionError = AsyncTransactionalOutboxCompletionError;