1pub mod batcher;
2pub mod channel;
3pub mod compensation;
4pub mod compression;
5#[cfg(feature = "queue-kafka")]
6pub mod kafka;
7pub mod manager;
8#[cfg(feature = "queue-nats")]
9pub mod nats;
10
11use crate::errors::Result;
12pub use crate::queue::channel::Channel;
13use async_trait::async_trait;
14pub use compensation::{Compensator, Identifiable};
15pub use manager::QueueManager;
16use tokio::sync::mpsc;
17
18#[cfg(test)]
19mod tests;
20
21#[derive(Debug, Clone)]
22pub enum AckAction {
24 Ack,
26 Nack(
28 String,
29 std::sync::Arc<Vec<u8>>,
30 std::sync::Arc<HashMap<String, String>>,
31 ), }
33
34use futures::future::BoxFuture;
35use std::collections::HashMap;
36
37pub type AckFn = Box<dyn FnOnce() -> BoxFuture<'static, Result<()>> + Send + Sync>;
38pub type NackFn = Box<dyn FnOnce(String) -> BoxFuture<'static, Result<()>> + Send + Sync>;
39
40pub const HEADER_ATTEMPT: &str = "x-attempt";
41pub const HEADER_CREATED_AT: &str = "x-created-at";
42pub const HEADER_NACK_REASON: &str = "x-nack-reason";
43
44#[derive(Debug, Clone, Copy, Default)]
45pub struct NackPolicy {
46 pub max_retries: u32,
47 pub backoff_ms: u64,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum NackDisposition {
52 Retry { next_attempt: u32 },
53 Dlq,
54}
55
56#[allow(dead_code)] pub(crate) fn parse_attempt(headers: &HashMap<String, String>) -> u32 {
58 headers
59 .get(HEADER_ATTEMPT)
60 .and_then(|v| v.parse::<u32>().ok())
61 .unwrap_or(0)
62}
63
64pub fn decide_nack(policy: NackPolicy, attempt: u32) -> NackDisposition {
65 if policy.max_retries > 0 && attempt < policy.max_retries {
66 NackDisposition::Retry {
67 next_attempt: attempt.saturating_add(1),
68 }
69 } else {
70 NackDisposition::Dlq
71 }
72}
73
74pub struct QueuedItem<T> {
76 pub inner: T,
77 ack_fn: Option<AckFn>,
78 nack_fn: Option<NackFn>,
79}
80
81impl<T> QueuedItem<T> {
82 pub fn new(inner: T) -> Self {
83 Self {
84 inner,
85 ack_fn: None,
86 nack_fn: None,
87 }
88 }
89
90 pub fn with_ack<A, N>(inner: T, ack: A, nack: N) -> Self
91 where
92 A: FnOnce() -> BoxFuture<'static, Result<()>> + Send + Sync + 'static,
93 N: FnOnce(String) -> BoxFuture<'static, Result<()>> + Send + Sync + 'static,
94 {
95 Self {
96 inner,
97 ack_fn: Some(Box::new(ack)),
98 nack_fn: Some(Box::new(nack)),
99 }
100 }
101
102 pub async fn ack(mut self) -> Result<()> {
103 if let Some(f) = self.ack_fn.take() {
104 f().await
105 } else {
106 Ok(())
107 }
108 }
109
110 pub async fn nack(mut self, reason: String) -> Result<()> {
111 if let Some(f) = self.nack_fn.take() {
112 f(reason).await
113 } else {
114 Ok(())
115 }
116 }
117
118 pub fn into_parts(self) -> (T, Option<AckFn>, Option<NackFn>) {
119 (self.inner, self.ack_fn, self.nack_fn)
120 }
121}
122
123impl<T> std::ops::Deref for QueuedItem<T> {
124 type Target = T;
125 fn deref(&self) -> &Self::Target {
126 &self.inner
127 }
128}
129
130impl<T: std::fmt::Debug> std::fmt::Debug for QueuedItem<T> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("QueuedItem")
133 .field("inner", &self.inner)
134 .finish()
135 }
136}
137
138impl<T: Identifiable> Identifiable for QueuedItem<T> {
139 fn get_id(&self) -> String {
140 self.inner.get_id()
141 }
142
143 fn partition_key(&self) -> String {
144 self.inner.partition_key()
145 }
146}
147
148#[derive(Clone)]
151pub struct Message {
152 pub payload: std::sync::Arc<Vec<u8>>,
153 pub id: String,
154 pub headers: std::sync::Arc<HashMap<String, String>>,
155 pub ack_tx: mpsc::Sender<(String, AckAction)>,
156}
157
158impl std::fmt::Debug for Message {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.debug_struct("Message")
161 .field("id", &self.id)
162 .field("payload", &self.payload)
163 .field("headers", &self.headers)
164 .finish()
165 }
166}
167
168impl Message {
169 pub async fn ack(&self) -> Result<()> {
170 self.ack_tx
172 .send((self.id.clone(), AckAction::Ack))
173 .await
174 .map_err(|_| {
175 crate::errors::error::QueueError::OperationFailed(Box::new(std::io::Error::other(
176 "Failed to send ACK signal",
177 )))
178 .into()
179 })
180 }
181
182 pub async fn nack(&self, reason: impl Into<String>) -> Result<()> {
183 let headers = self.headers.clone();
184 self.ack_tx
185 .send((
186 self.id.clone(),
187 AckAction::Nack(reason.into(), self.payload.clone(), headers),
188 ))
189 .await
190 .map_err(|_| {
191 crate::errors::error::QueueError::OperationFailed(Box::new(std::io::Error::other(
192 "Failed to send NACK signal",
193 )))
194 .into()
195 })
196 }
197}
198
199#[async_trait]
200pub trait MqBackend: Send + Sync {
201 async fn publish(&self, topic: &str, key: Option<&str>, payload: &[u8]) -> Result<()> {
202 self.publish_with_headers(topic, key, payload, &HashMap::new())
203 .await
204 }
205
206 async fn publish_with_headers(
207 &self,
208 topic: &str,
209 key: Option<&str>,
210 payload: &[u8],
211 headers: &HashMap<String, String>,
212 ) -> Result<()>;
213
214 async fn publish_batch(&self, topic: &str, items: &[(Option<String>, Vec<u8>)]) -> Result<()> {
215 for (key, payload) in items {
216 self.publish(topic, key.as_deref(), payload).await?;
217 }
218 Ok(())
219 }
220
221 async fn publish_batch_with_headers(
222 &self,
223 topic: &str,
224 items: &[(Option<String>, Vec<u8>, HashMap<String, String>)],
225 ) -> Result<()> {
226 for (key, payload, headers) in items {
227 self.publish_with_headers(topic, key.as_deref(), payload, headers)
228 .await?;
229 }
230 Ok(())
231 }
232
233 async fn subscribe(&self, topic: &str, sender: mpsc::Sender<Message>) -> Result<()>;
234 async fn clean_storage(&self) -> Result<()>;
235 async fn send_to_dlq(&self, topic: &str, id: &str, payload: &[u8], reason: &str) -> Result<()>;
237
238 async fn read_dlq(
241 &self,
242 topic: &str,
243 count: usize,
244 ) -> Result<Vec<(String, Vec<u8>, String, String)>>;
245}