use reliar_core::{Classify, MessageId};
use tracing::Instrument as _;
use crate::claim::{InboxClaim, InboxFailure, InboxOutcome};
use crate::error::InboxProcessError;
use crate::handler::InboxHandler;
use crate::message::InboxMessage;
use crate::purge::{InboxPurgeReport, InboxPurgeRequest};
use crate::record::InboxRecord;
use crate::scope::InboxScope;
pub trait InboxStore<Tx>: Send + Sync {
type Error: std::error::Error + Send + Sync + 'static + Classify;
fn claim(
&self,
tx: &mut Tx,
scope: &InboxScope,
message: InboxMessage<'_>,
) -> impl Future<Output = Result<InboxClaim, Self::Error>> + Send;
fn complete(
&self,
tx: &mut Tx,
scope: &InboxScope,
id: MessageId,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn fail(
&self,
scope: &InboxScope,
message: InboxMessage<'_>,
error: &(dyn std::error::Error + 'static),
) -> impl Future<Output = Result<InboxFailure, Self::Error>> + Send;
fn find(
&self,
scope: &InboxScope,
id: MessageId,
) -> impl Future<Output = Result<Option<InboxRecord>, Self::Error>> + Send;
fn purge(
&self,
request: InboxPurgeRequest,
) -> impl Future<Output = Result<InboxPurgeReport, Self::Error>> + Send;
#[allow(
clippy::type_complexity,
reason = "the return type names exactly the two outcomes `process` can produce; a type \
alias would need `Self::Error` and `H::Error` as parameters and read no clearer"
)]
fn process<H>(
&self,
tx: &mut Tx,
scope: &InboxScope,
message: InboxMessage<'_>,
handler: &H,
) -> impl Future<
Output = Result<InboxOutcome<H::Output>, InboxProcessError<Self::Error, H::Error>>,
> + Send
where
H: InboxHandler<Tx> + Sync,
Tx: Send,
{
let span = tracing::info_span!(
"reliar.inbox.process",
inbox.scope = %scope,
message.id = %message.id,
message.r#type = %message.message_type,
inbox.outcome = tracing::field::Empty,
inbox.record_id = tracing::field::Empty,
);
let recording_span = span.clone();
async move {
let outcome = match self
.claim(tx, scope, message)
.await
.map_err(InboxProcessError::Store)?
{
InboxClaim::AlreadyCompleted { completed_at } => {
InboxOutcome::AlreadyCompleted { completed_at }
}
InboxClaim::InProgress => InboxOutcome::InProgress,
InboxClaim::Dead {
id,
attempts,
dead_at,
} => InboxOutcome::Dead {
id,
attempts,
dead_at,
},
InboxClaim::Claimed { .. } => {
let output = handler
.handle(tx)
.await
.map_err(InboxProcessError::Handler)?;
self.complete(tx, scope, message.id)
.await
.map_err(InboxProcessError::Store)?;
InboxOutcome::Processed(output)
}
};
recording_span.record("inbox.outcome", outcome_label(&outcome));
if let InboxOutcome::Dead { id, .. } = &outcome {
recording_span.record("inbox.record_id", tracing::field::display(id));
}
Ok(outcome)
}
.instrument(span)
}
}
fn outcome_label<T>(outcome: &InboxOutcome<T>) -> &'static str {
match outcome {
InboxOutcome::Processed(_) => "processed",
InboxOutcome::AlreadyCompleted { .. } => "already_completed",
InboxOutcome::InProgress => "in_progress",
InboxOutcome::Dead { .. } => "dead",
}
}