use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc, time::Duration};
use super::context::Context;
type AfterFut = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum HandlerResult {
Ack,
Nack {
requeue: bool,
},
NackAfter {
delay: Duration,
},
}
impl HandlerResult {
#[must_use]
pub(crate) const fn retry() -> Self {
Self::Nack { requeue: true }
}
#[must_use]
pub(crate) const fn retry_after(delay: Duration) -> Self {
Self::NackAfter { delay }
}
#[must_use]
pub(crate) const fn drop() -> Self {
Self::Nack { requeue: false }
}
}
#[must_use]
pub struct HandlerOutcome {
outcome: HandlerResult,
after: Option<AfterFut>,
}
impl HandlerOutcome {
pub const fn ack() -> Self {
Self {
outcome: HandlerResult::Ack,
after: None,
}
}
pub const fn retry() -> Self {
Self {
outcome: HandlerResult::retry(),
after: None,
}
}
pub const fn retry_after(delay: Duration) -> Self {
Self {
outcome: HandlerResult::retry_after(delay),
after: None,
}
}
pub const fn drop() -> Self {
Self {
outcome: HandlerResult::drop(),
after: None,
}
}
pub fn and_after<F>(self, fut: F) -> Self
where
F: Future<Output = ()> + Send + 'static,
{
Self {
outcome: self.outcome,
after: Some(Box::pin(fut)),
}
}
#[must_use]
pub const fn is_ack(&self) -> bool {
matches!(self.outcome, HandlerResult::Ack)
}
#[must_use]
pub const fn is_retry(&self) -> bool {
matches!(
self.outcome,
HandlerResult::Nack { requeue: true } | HandlerResult::NackAfter { .. }
)
}
#[must_use]
pub const fn is_drop(&self) -> bool {
matches!(self.outcome, HandlerResult::Nack { requeue: false })
}
#[must_use]
pub const fn retry_delay(&self) -> Option<Duration> {
match self.outcome {
HandlerResult::NackAfter { delay } => Some(delay),
HandlerResult::Ack | HandlerResult::Nack { .. } => None,
}
}
pub(crate) const fn outcome(&self) -> HandlerResult {
self.outcome
}
pub(crate) const fn has_after(&self) -> bool {
self.after.is_some()
}
pub(crate) fn take_after(&mut self) -> Option<AfterFut> {
self.after.take()
}
}
impl std::fmt::Debug for HandlerOutcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HandlerOutcome")
.field("outcome", &self.outcome)
.field("after", &self.after.is_some())
.finish()
}
}
impl From<HandlerResult> for HandlerOutcome {
fn from(outcome: HandlerResult) -> Self {
Self {
outcome,
after: None,
}
}
}
impl From<Infallible> for HandlerOutcome {
fn from(never: Infallible) -> Self {
match never {}
}
}
#[doc(hidden)]
pub trait IntoOutcome {
fn into_outcome(self) -> HandlerOutcome;
}
impl IntoOutcome for HandlerOutcome {
fn into_outcome(self) -> HandlerOutcome {
self
}
}
impl IntoOutcome for () {
fn into_outcome(self) -> HandlerOutcome {
HandlerOutcome::ack()
}
}
impl<E> IntoOutcome for Result<(), E> {
fn into_outcome(self) -> HandlerOutcome {
match self {
Ok(()) => HandlerOutcome::ack(),
Err(_) => HandlerOutcome::drop(),
}
}
}
impl<E> IntoOutcome for Result<HandlerOutcome, E> {
fn into_outcome(self) -> HandlerOutcome {
self.unwrap_or_else(|_| HandlerOutcome::drop())
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot handle `{M}`",
note = "a handler is a named type with an `impl Handler<{M}>` whose `handle` method returns \
the settlement, or a closure `|msg: &{M}, ctx: &mut Context| async {{ .. }}` whose \
future does not borrow its arguments"
)]
pub trait Handler<M: ?Sized, C = (), S = ()>: Send + Sync {
fn handle(
&self,
msg: &M,
ctx: &mut Context<'_, C, S>,
) -> impl Future<Output = HandlerOutcome> + Send;
}
impl<M: ?Sized, C, S, F, Fut> Handler<M, C, S> for F
where
F: Fn(&M, &mut Context<'_, C, S>) -> Fut + Send + Sync,
Fut: Future + Send,
Fut::Output: IntoOutcome,
{
fn handle(
&self,
msg: &M,
ctx: &mut Context<'_, C, S>,
) -> impl Future<Output = HandlerOutcome> + Send {
let fut = (self)(msg, ctx);
async move { fut.await.into_outcome() }
}
}
impl<M, C, S, H> Handler<M, C, S> for Arc<H>
where
H: Handler<M, C, S>,
{
fn handle(
&self,
msg: &M,
ctx: &mut Context<'_, C, S>,
) -> impl Future<Output = HandlerOutcome> + Send {
(**self).handle(msg, ctx)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{HandlerOutcome, HandlerResult, IntoOutcome};
#[test]
fn constructors_map_to_statuses() {
assert_eq!(HandlerOutcome::ack().outcome(), HandlerResult::Ack);
assert_eq!(
HandlerOutcome::retry().outcome(),
HandlerResult::Nack { requeue: true }
);
assert_eq!(
HandlerOutcome::drop().outcome(),
HandlerResult::Nack { requeue: false }
);
assert_eq!(
HandlerOutcome::retry_after(Duration::from_secs(2)).outcome(),
HandlerResult::NackAfter {
delay: Duration::from_secs(2)
}
);
}
#[test]
fn predicates_read_the_status() {
assert!(HandlerOutcome::ack().is_ack());
assert!(HandlerOutcome::retry().is_retry());
assert!(HandlerOutcome::retry_after(Duration::from_secs(1)).is_retry());
assert!(HandlerOutcome::drop().is_drop());
assert_eq!(
HandlerOutcome::retry_after(Duration::from_secs(1)).retry_delay(),
Some(Duration::from_secs(1))
);
assert_eq!(HandlerOutcome::retry().retry_delay(), None);
}
#[test]
fn into_outcome_covers_every_return_shape() {
assert_outcome(().into_outcome(), HandlerResult::Ack, false);
assert_outcome(Ok::<(), ()>(()).into_outcome(), HandlerResult::Ack, false);
assert_outcome(
Err::<(), ()>(()).into_outcome(),
HandlerResult::drop(),
false,
);
assert_outcome(
Ok::<HandlerOutcome, ()>(HandlerOutcome::retry()).into_outcome(),
HandlerResult::retry(),
false,
);
assert_outcome(
Err::<HandlerOutcome, ()>(()).into_outcome(),
HandlerResult::drop(),
false,
);
let with_after = HandlerOutcome::ack().and_after(async {});
assert_outcome(with_after.into_outcome(), HandlerResult::Ack, true);
let ok: Result<HandlerOutcome, ()> = Ok(HandlerOutcome::drop().and_after(async {}));
assert_outcome(ok.into_outcome(), HandlerResult::drop(), true);
let err: Result<HandlerOutcome, ()> = Err(());
assert_outcome(err.into_outcome(), HandlerResult::drop(), false);
}
#[test]
fn the_debug_form_says_whether_a_continuation_rides_along() {
let outcome = HandlerOutcome::ack().and_after(async {});
assert!(format!("{outcome:?}").contains("after: true"));
let plain = HandlerOutcome::retry();
assert!(format!("{plain:?}").contains("after: false"));
}
fn assert_outcome(mut outcome: HandlerOutcome, status: HandlerResult, has_after: bool) {
assert_eq!(outcome.outcome(), status);
assert_eq!(outcome.take_after().is_some(), has_after);
}
}