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}