paladin/
acker.rs

1//! Provides a trait for acknowledging messages in an asynchronous context.
2//!
3//! Acknowledgement is a common pattern in distributed systems, especially in
4//! message-driven architectures. This module introduces a trait, `Acker`, which
5//! abstracts the act of acknowledging a message. This abstraction allows for a
6//! consistent interface across different components or systems that require
7//! message acknowledgement.
8//!
9//! The trait is designed with async/await in mind, ensuring that
10//! acknowledgements can be handled in an asynchronous manner without blocking
11//! the main thread.
12//!
13//! # Features:
14//! - **Asynchronous**: Uses async/await for non-blocking operations.
15//! - **Generic Implementation for Boxed Types**: Allows for easy integration
16//!   with boxed types, ensuring flexibility in usage.
17//!
18//! # Examples
19//!
20//! Implementing the `Acker` trait for a custom type:
21//!
22//! ```
23//! use paladin::acker::Acker;
24//! use anyhow::Result;
25//! use async_trait::async_trait;
26//!
27//! struct MyAcker;
28//!
29//! #[async_trait]
30//! impl Acker for MyAcker {
31//!     async fn ack(&self) -> Result<()> {
32//!         // Custom acknowledgement logic here...
33//!         Ok(())
34//!     }
35//!
36//!     async fn nack(&self) -> Result<()> {
37//!         // Custom negative acknowledgement logic here...
38//!         Ok(())
39//!     }
40//! }
41//! ```
42//!
43//! Using the `Acker` trait with boxed types:
44//!
45//! ```
46//! # use paladin::acker::Acker;
47//! # use anyhow::Result;
48//! # use async_trait::async_trait;
49//! # struct MyAcker;
50//! #
51//! # #[async_trait]
52//! # impl Acker for MyAcker {
53//! #    async fn ack(&self) -> Result<()> {
54//! #        // Custom acknowledgement logic here...
55//! #        Ok(())
56//! #    }
57//! #
58//! #    async fn nack(&self) -> Result<()> {
59//! #        // Custom negative acknowledgement logic here...
60//! #        Ok(())
61//! #    }
62//! # }
63//! # #[tokio::main]
64//! # async fn main() -> Result<()> {
65//! let my_acker: Box<MyAcker> = Box::new(MyAcker);
66//! my_acker.ack().await?;
67//! # Ok(())
68//! # }
69//! ```
70use std::sync::Arc;
71
72use anyhow::Result;
73use async_trait::async_trait;
74use futures::TryFutureExt;
75
76/// Represents a generic behavior for acknowledging messages.
77///
78/// This trait provides a unified interface for components that require message
79/// acknowledgement. Implementers of this trait should provide the actual logic
80/// for acknowledging a message in the `ack`  and `nack` methods.
81#[async_trait]
82pub trait Acker: Send + Sync {
83    async fn ack(&self) -> Result<()>;
84
85    async fn nack(&self) -> Result<()>;
86}
87
88/// Provides an implementation of the `Acker` trait for boxed types.
89///
90/// This allows for flexibility in using the `Acker` trait with dynamically
91/// dispatched types.
92#[async_trait]
93impl<T: Acker + ?Sized> Acker for Box<T> {
94    async fn ack(&self) -> Result<()> {
95        (**self).ack().await
96    }
97
98    async fn nack(&self) -> Result<()> {
99        (**self).nack().await
100    }
101}
102
103/// Provides an implementation of the `Acker` trait for `Arc` types.
104#[async_trait]
105impl<T: Acker + ?Sized> Acker for Arc<T> {
106    async fn ack(&self) -> Result<()> {
107        (**self).ack().await
108    }
109
110    async fn nack(&self) -> Result<()> {
111        (**self).nack().await
112    }
113}
114
115/// An `Acker` implementation that composes two `Acker` instances.
116///
117/// The `ack` and `nack` methods on the second `Acker` instance will only be
118/// called if the first `Acker` instance succeeds.
119pub struct ComposedAcker<A, B> {
120    fst: A,
121    snd: B,
122}
123
124impl<A, B> ComposedAcker<A, B> {
125    pub fn new(fst: A, snd: B) -> Self {
126        Self { fst, snd }
127    }
128}
129
130#[async_trait]
131impl<A: Acker, B: Acker> Acker for ComposedAcker<A, B> {
132    /// Acknowledge the second `Acker` instance if the first `Acker` instance
133    /// succeeds.
134    async fn ack(&self) -> Result<()> {
135        self.fst.ack().and_then(|_| self.snd.ack()).await
136    }
137
138    /// Negative acknowledge the second `Acker` instance if the first `Acker`
139    /// instance succeeds.
140    async fn nack(&self) -> Result<()> {
141        self.fst.nack().and_then(|_| self.snd.nack()).await
142    }
143}
144
145/// Acker implementation that does nothing.
146///
147/// Useful for testing purposes where channels are emulated in-memory.
148#[derive(Default)]
149pub struct NoopAcker;
150
151impl NoopAcker {
152    pub fn new() -> Self {
153        Self
154    }
155}
156
157#[async_trait]
158impl Acker for NoopAcker {
159    async fn ack(&self) -> Result<()> {
160        // noop
161        Ok(())
162    }
163
164    async fn nack(&self) -> Result<()> {
165        // noop
166        Ok(())
167    }
168}