1use crate::error::MqError;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::collections::{HashMap, VecDeque};
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tokio::sync::{Notify, RwLock};
8
9#[async_trait]
18pub trait MessageQueue: Send + Sync {
19 async fn publish(&self, topic: &str, message: &[u8]) -> Result<(), MqError>;
21
22 async fn consume(&self, topic: &str) -> Result<Option<Message>, MqError>;
24
25 async fn ack(&self, message_id: &str) -> Result<(), MqError>;
27
28 async fn subscribe(&self, topic: &str) -> Result<(), MqError>;
30
31 async fn nack(&self, _message_id: &str) -> Result<(), MqError> {
38 Err(MqError::NotSupported("nack not supported".to_string()))
39 }
40
41 async fn reject(&self, _message_id: &str) -> Result<(), MqError> {
45 Err(MqError::NotSupported("reject not supported".to_string()))
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct Message {
58 pub topic: String,
59 pub payload: Vec<u8>,
60 pub key: Option<String>,
61 pub timestamp: i64,
62 pub headers: HashMap<String, String>,
63 #[serde(default)]
64 pub id: String,
65 #[serde(default)]
67 pub retry_count: u32,
68}
69
70impl Message {
71 pub fn new(topic: impl Into<String>, payload: Vec<u8>) -> Self {
72 Self {
73 topic: topic.into(),
74 payload,
75 key: None,
76 timestamp: current_timestamp(),
77 headers: HashMap::new(),
78 id: String::new(),
79 retry_count: 0,
80 }
81 }
82
83 pub fn with_key(mut self, key: impl Into<String>) -> Self {
84 self.key = Some(key.into());
85 self
86 }
87
88 pub fn text(&self) -> Option<&str> {
89 std::str::from_utf8(&self.payload).ok()
90 }
91
92 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
93 serde_json::from_slice(&self.payload).ok()
94 }
95
96 pub fn text_message(topic: impl Into<String>, text: impl Into<String>) -> Self {
97 Self::new(topic, text.into().into_bytes())
98 }
99
100 pub fn json_message<T: serde::Serialize>(
101 topic: impl Into<String>,
102 data: &T,
103 ) -> Result<Self, MqError> {
104 let payload = serde_json::to_vec(data)?;
105 Ok(Self::new(topic, payload))
106 }
107}
108
109fn current_timestamp() -> i64 {
115 use std::time::{SystemTime, UNIX_EPOCH};
116 match SystemTime::now().duration_since(UNIX_EPOCH) {
117 Ok(d) => d.as_millis() as i64,
118 Err(e) => {
119 eprintln!(
122 "WARN: current_timestamp: system time before UNIX_EPOCH: {} (duration_secs={})",
123 e,
124 e.duration().as_secs()
125 );
126 0
127 }
128 }
129}
130
131pub struct QueueConfig {
136 pub provider: MqProvider,
137 pub brokers: Vec<String>,
138 pub group_id: Option<String>,
139 pub username: Option<String>,
140 pub password: Option<String>,
141}
142
143impl Default for QueueConfig {
144 fn default() -> Self {
145 Self {
146 provider: MqProvider::Kafka(KafkaConfig::default()),
147 brokers: vec!["localhost:9092".to_string()],
148 group_id: None,
149 username: None,
150 password: None,
151 }
152 }
153}
154
155impl QueueConfig {
156 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn with_provider(mut self, provider: MqProvider) -> Self {
161 self.provider = provider;
162 self
163 }
164
165 pub fn with_brokers(mut self, brokers: Vec<String>) -> Self {
166 self.brokers = brokers;
167 self
168 }
169
170 pub fn with_group(mut self, group: impl Into<String>) -> Self {
171 self.group_id = Some(group.into());
172 self
173 }
174
175 pub fn with_auth(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
176 self.username = Some(username.into());
177 self.password = Some(password.into());
178 self
179 }
180}
181
182#[derive(Debug, Clone)]
183pub enum MqProvider {
184 Kafka(KafkaConfig),
185 RabbitMQ(RabbitConfig),
186 RocketMQ(RocketConfig),
187 ActiveMQ(ActiveConfig),
188 Nats(NatsConfig),
189 Pulsar(PulsarConfig),
190}
191
192#[derive(Debug, Clone, Default)]
193pub struct KafkaConfig {
194 pub client_id: Option<String>,
195 pub acks: Option<String>,
196 pub retries: Option<u32>,
197}
198
199#[derive(Debug, Clone, Default)]
200pub struct RabbitConfig {
201 pub virtual_host: Option<String>,
202}
203
204#[derive(Debug, Clone, Default)]
205pub struct RocketConfig {
206 pub namespace: Option<String>,
207}
208
209#[derive(Debug, Clone, Default)]
210pub struct ActiveConfig {
211 pub broker_url: Option<String>,
212}
213
214#[derive(Debug, Clone, Default)]
215pub struct NatsConfig {
216 pub name: Option<String>,
217}
218
219#[derive(Debug, Clone, Default)]
220pub struct PulsarConfig {
221 pub service_url: Option<String>,
222}
223
224#[derive(Debug, Clone)]
237pub struct ReconnectPolicy {
238 pub max_retries: u32,
240 pub initial_delay_ms: u64,
242 pub max_delay_ms: u64,
244 pub multiplier: f64,
246}
247
248impl Default for ReconnectPolicy {
249 fn default() -> Self {
250 Self {
251 max_retries: 5,
252 initial_delay_ms: 100,
253 max_delay_ms: 10_000,
254 multiplier: 2.0,
255 }
256 }
257}
258
259impl ReconnectPolicy {
260 pub fn new() -> Self {
261 Self::default()
262 }
263
264 pub fn next_delay(&self, attempt: u32) -> Duration {
272 let delay_ms = (self.initial_delay_ms as f64) * self.multiplier.powi(attempt as i32);
273 let delay_ms = delay_ms.max(0.0).min(self.max_delay_ms as f64);
275 Duration::from_millis(delay_ms as u64)
276 }
277}
278
279#[derive(Debug, Clone, Default)]
283pub struct ReconnectState {
284 pub attempts: u32,
286 pub last_reconnect: Option<Instant>,
288}
289
290#[derive(Debug, Clone)]
301pub struct BackpressurePolicy {
302 pub max_queue_size: usize,
304 pub on_overflow: OverflowStrategy,
306}
307
308impl Default for BackpressurePolicy {
309 fn default() -> Self {
310 Self {
311 max_queue_size: 10_000,
312 on_overflow: OverflowStrategy::Reject,
313 }
314 }
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub enum OverflowStrategy {
325 Block,
327 DropOldest,
329 DropNewest,
331 Reject,
333}
334
335pub struct InMemoryQueue {
340 inner: Arc<RwLock<InMemoryQueueInner>>,
341}
342
343impl Clone for InMemoryQueue {
344 fn clone(&self) -> Self {
345 Self {
346 inner: Arc::clone(&self.inner),
347 }
348 }
349}
350
351struct InMemoryQueueInner {
352 queues: HashMap<String, VecDeque<Message>>,
354 in_flight: HashMap<String, Message>,
356 subscribers: HashMap<String, usize>,
358 next_id: u64,
360 max_messages_per_topic: usize,
363 dead_letters: HashMap<String, VecDeque<Message>>,
365 max_retries: u32,
367 backpressure: Option<BackpressurePolicy>,
369 notify: HashMap<String, Arc<Notify>>,
371}
372
373const DEFAULT_MAX_MESSAGES_PER_TOPIC: usize = 100_000;
375
376const DEFAULT_MAX_RETRIES: u32 = 3;
378
379impl InMemoryQueue {
380 pub fn new() -> Self {
381 Self::with_max_messages_per_topic(DEFAULT_MAX_MESSAGES_PER_TOPIC)
382 }
383
384 pub fn with_max_messages_per_topic(max: usize) -> Self {
389 Self {
390 inner: Arc::new(RwLock::new(InMemoryQueueInner {
391 queues: HashMap::new(),
392 in_flight: HashMap::new(),
393 subscribers: HashMap::new(),
394 next_id: 1,
395 max_messages_per_topic: max,
396 dead_letters: HashMap::new(),
397 max_retries: DEFAULT_MAX_RETRIES,
398 backpressure: None,
399 notify: HashMap::new(),
400 })),
401 }
402 }
403
404 pub fn with_max_retries(max_retries: u32) -> Self {
409 Self {
410 inner: Arc::new(RwLock::new(InMemoryQueueInner {
411 queues: HashMap::new(),
412 in_flight: HashMap::new(),
413 subscribers: HashMap::new(),
414 next_id: 1,
415 max_messages_per_topic: DEFAULT_MAX_MESSAGES_PER_TOPIC,
416 dead_letters: HashMap::new(),
417 max_retries,
418 backpressure: None,
419 notify: HashMap::new(),
420 })),
421 }
422 }
423
424 pub fn with_backpressure(policy: BackpressurePolicy) -> Self {
429 let max = policy.max_queue_size;
430 Self {
431 inner: Arc::new(RwLock::new(InMemoryQueueInner {
432 queues: HashMap::new(),
433 in_flight: HashMap::new(),
434 subscribers: HashMap::new(),
435 next_id: 1,
436 max_messages_per_topic: max,
437 dead_letters: HashMap::new(),
438 max_retries: DEFAULT_MAX_RETRIES,
439 backpressure: Some(policy),
440 notify: HashMap::new(),
441 })),
442 }
443 }
444
445 pub async fn message_count(&self, topic: &str) -> usize {
446 let inner = self.inner.read().await;
447 inner.queues.get(topic).map(|q| q.len()).unwrap_or(0)
448 }
449
450 pub async fn subscriber_count(&self, topic: &str) -> usize {
451 let inner = self.inner.read().await;
452 *inner.subscribers.get(topic).unwrap_or(&0)
453 }
454
455 pub async fn in_flight_count(&self) -> usize {
456 let inner = self.inner.read().await;
457 inner.in_flight.len()
458 }
459
460 pub async fn dead_letter_count(&self, topic: &str) -> usize {
464 let inner = self.inner.read().await;
465 inner.dead_letters.get(topic).map(|q| q.len()).unwrap_or(0)
466 }
467
468 pub async fn consume_dead_letter(&self, topic: &str) -> Option<Message> {
473 let mut inner = self.inner.write().await;
474 if let Some(dq) = inner.dead_letters.get_mut(topic) {
475 return dq.pop_front();
476 }
477 None
478 }
479
480 pub async fn collect_all_dead_letters(&self) -> Vec<Message> {
484 let inner = self.inner.read().await;
485 let mut result = Vec::new();
486 for dq in inner.dead_letters.values() {
487 for msg in dq {
488 result.push(msg.clone());
489 }
490 }
491 result
492 }
493
494 pub async fn remove_dead_letter(&self, message_id: &str) -> bool {
499 let mut inner = self.inner.write().await;
500 for dq in inner.dead_letters.values_mut() {
501 if let Some(pos) = dq.iter().position(|m| m.id == message_id) {
502 dq.remove(pos);
503 return true;
504 }
505 }
506 false
507 }
508
509 pub async fn requeue_dead_letter(&self, message_id: &str) -> Result<(), MqError> {
514 let mut inner = self.inner.write().await;
515 for dq in inner.dead_letters.values_mut() {
517 let mut found_idx = None;
518 for (idx, m) in dq.iter().enumerate() {
519 if m.id == message_id {
520 found_idx = Some(idx);
521 break;
522 }
523 }
524 if let Some(idx) = found_idx {
525 let mut msg = dq.remove(idx).expect("checked: idx exists");
526 msg.retry_count = 0;
528 inner
530 .queues
531 .entry(msg.topic.clone())
532 .or_insert_with(VecDeque::new)
533 .push_back(msg);
534 return Ok(());
535 }
536 }
537 Err(MqError::NotSupported(format!(
538 "Dead letter not found: {}",
539 message_id
540 )))
541 }
542}
543
544impl Default for InMemoryQueue {
545 fn default() -> Self {
546 Self::new()
547 }
548}
549
550#[async_trait]
551impl MessageQueue for InMemoryQueue {
552 async fn publish(&self, topic: &str, message: &[u8]) -> Result<(), MqError> {
553 let strategy = {
555 let inner = self.inner.read().await;
556 inner
557 .backpressure
558 .as_ref()
559 .map(|p| p.on_overflow)
560 .unwrap_or(OverflowStrategy::Reject)
561 };
562
563 match strategy {
564 OverflowStrategy::Block => self.publish_with_block(topic, message).await,
565 _ => self.publish_immediate(topic, message, strategy).await,
566 }
567 }
568
569 async fn consume(&self, topic: &str) -> Result<Option<Message>, MqError> {
570 let mut inner = self.inner.write().await;
571 let queue = inner
572 .queues
573 .entry(topic.to_string())
574 .or_insert_with(VecDeque::new);
575 if let Some(msg) = queue.pop_front() {
576 inner.in_flight.insert(msg.id.clone(), msg.clone());
577 if let Some(notify) = inner.notify.get(topic) {
579 notify.notify_one();
580 }
581 Ok(Some(msg))
582 } else {
583 Ok(None)
584 }
585 }
586
587 async fn ack(&self, message_id: &str) -> Result<(), MqError> {
588 let mut inner = self.inner.write().await;
589 inner.in_flight.remove(message_id).ok_or_else(|| {
590 MqError::NotSupported(format!("Message not found for ack: {}", message_id))
591 })?;
592 Ok(())
593 }
594
595 async fn subscribe(&self, topic: &str) -> Result<(), MqError> {
596 let mut inner = self.inner.write().await;
597 *inner.subscribers.entry(topic.to_string()).or_insert(0) += 1;
598 Ok(())
599 }
600
601 async fn nack(&self, message_id: &str) -> Result<(), MqError> {
602 let mut inner = self.inner.write().await;
603 let mut msg = inner.in_flight.remove(message_id).ok_or_else(|| {
604 MqError::NotSupported(format!("Message not found for nack: {}", message_id))
605 })?;
606
607 msg.retry_count = msg.retry_count.saturating_add(1);
609
610 if msg.retry_count >= inner.max_retries {
612 inner
613 .dead_letters
614 .entry(msg.topic.clone())
615 .or_insert_with(VecDeque::new)
616 .push_back(msg);
617 } else {
618 inner
621 .queues
622 .entry(msg.topic.clone())
623 .or_insert_with(VecDeque::new)
624 .push_back(msg);
625 }
626 Ok(())
627 }
628
629 async fn reject(&self, message_id: &str) -> Result<(), MqError> {
630 let mut inner = self.inner.write().await;
631 let msg = inner.in_flight.remove(message_id).ok_or_else(|| {
632 MqError::NotSupported(format!("Message not found for reject: {}", message_id))
633 })?;
634 inner
636 .dead_letters
637 .entry(msg.topic.clone())
638 .or_insert_with(VecDeque::new)
639 .push_back(msg);
640 Ok(())
641 }
642}
643
644impl InMemoryQueue {
645 async fn publish_immediate(
649 &self,
650 topic: &str,
651 message: &[u8],
652 strategy: OverflowStrategy,
653 ) -> Result<(), MqError> {
654 let mut inner = self.inner.write().await;
655 let current_count = inner.queues.get(topic).map(|q| q.len()).unwrap_or(0);
657 if current_count >= inner.max_messages_per_topic {
658 match strategy {
659 OverflowStrategy::DropOldest => {
660 let queue = inner
662 .queues
663 .entry(topic.to_string())
664 .or_insert_with(VecDeque::new);
665 queue.pop_front();
666 }
667 OverflowStrategy::DropNewest => {
668 return Ok(());
670 }
671 OverflowStrategy::Reject => {
672 return Err(MqError::Publish(format!(
673 "topic '{}' is full: {} >= {} messages (H-3 protection)",
674 topic, current_count, inner.max_messages_per_topic
675 )));
676 }
677 OverflowStrategy::Block => {
678 return Err(MqError::Publish(format!(
680 "topic '{}' overflow with Block strategy: use publish_with_block instead",
681 topic
682 )));
683 }
684 }
685 }
686 let id = format!("msg-{}", inner.next_id);
688 inner.next_id = inner
690 .next_id
691 .checked_add(1)
692 .ok_or_else(|| MqError::Publish("message id overflow: u64::MAX reached".to_string()))?;
693 let msg = Message {
694 id,
695 retry_count: 0,
696 ..Message::new(topic, message.to_vec())
697 };
698 inner
699 .queues
700 .entry(topic.to_string())
701 .or_insert_with(VecDeque::new)
702 .push_back(msg);
703 Ok(())
704 }
705
706 async fn publish_with_block(&self, topic: &str, message: &[u8]) -> Result<(), MqError> {
710 loop {
711 let notify = {
712 let mut inner = self.inner.write().await;
713 let current_count = inner.queues.get(topic).map(|q| q.len()).unwrap_or(0);
714 if current_count < inner.max_messages_per_topic {
715 let id = format!("msg-{}", inner.next_id);
717 inner.next_id = inner.next_id.checked_add(1).ok_or_else(|| {
718 MqError::Publish("message id overflow: u64::MAX reached".to_string())
719 })?;
720 let msg = Message {
721 id,
722 retry_count: 0,
723 ..Message::new(topic, message.to_vec())
724 };
725 inner
726 .queues
727 .entry(topic.to_string())
728 .or_insert_with(VecDeque::new)
729 .push_back(msg);
730 return Ok(());
731 }
732 inner
734 .notify
735 .entry(topic.to_string())
736 .or_insert_with(|| Arc::new(Notify::new()))
737 .clone()
738 };
739 notify.notified().await;
742 }
743 }
744}
745
746pub struct QueueWrapper {
751 queue: Box<dyn MessageQueue>,
752 reconnect: Option<ReconnectPolicy>,
754}
755
756impl QueueWrapper {
757 pub fn new(provider: MqProvider) -> Self {
758 let queue: Box<dyn MessageQueue> = match provider {
759 MqProvider::Kafka(_) => Box::new(crate::kafka::InMemoryKafkaQueue::new()),
760 MqProvider::RabbitMQ(_) => Box::new(crate::rabbitmq::InMemoryRabbitmqQueue::new()),
761 MqProvider::RocketMQ(_) => Box::new(crate::rocketmq::InMemoryRocketmqQueue::new()),
762 MqProvider::ActiveMQ(_) => Box::new(crate::activemq::InMemoryActivemqQueue::new()),
763 MqProvider::Nats(_) => Box::new(crate::nats::InMemoryNatsQueue::new()),
764 MqProvider::Pulsar(_) => Box::new(crate::pulsar::InMemoryPulsarQueue::new()),
765 };
766 Self {
767 queue,
768 reconnect: None,
769 }
770 }
771
772 #[cfg(test)]
774 pub(crate) fn with_queue(queue: Box<dyn MessageQueue>) -> Self {
775 Self {
776 queue,
777 reconnect: None,
778 }
779 }
780
781 pub fn with_reconnect(mut self, policy: ReconnectPolicy) -> Self {
786 self.reconnect = Some(policy);
787 self
788 }
789
790 pub async fn publish(&self, topic: &str, message: &[u8]) -> Result<(), MqError> {
791 if let Some(policy) = &self.reconnect {
792 let mut attempts = 0u32;
793 loop {
794 match self.queue.publish(topic, message).await {
795 Ok(()) => return Ok(()),
796 Err(MqError::Connection(_)) if attempts < policy.max_retries => {
797 let delay = policy.next_delay(attempts);
798 tokio::time::sleep(delay).await;
799 attempts += 1;
800 }
801 Err(e) => return Err(e),
802 }
803 }
804 } else {
805 self.queue.publish(topic, message).await
806 }
807 }
808
809 pub async fn consume(&self, topic: &str) -> Result<Option<Message>, MqError> {
810 if let Some(policy) = &self.reconnect {
811 let mut attempts = 0u32;
812 loop {
813 match self.queue.consume(topic).await {
814 Ok(msg) => return Ok(msg),
815 Err(MqError::Connection(_)) if attempts < policy.max_retries => {
816 let delay = policy.next_delay(attempts);
817 tokio::time::sleep(delay).await;
818 attempts += 1;
819 }
820 Err(e) => return Err(e),
821 }
822 }
823 } else {
824 self.queue.consume(topic).await
825 }
826 }
827
828 pub async fn ack(&self, message_id: &str) -> Result<(), MqError> {
829 self.queue.ack(message_id).await
830 }
831
832 pub async fn subscribe(&self, topic: &str) -> Result<(), MqError> {
833 self.queue.subscribe(topic).await
834 }
835
836 pub async fn nack(&self, message_id: &str) -> Result<(), MqError> {
840 self.queue.nack(message_id).await
841 }
842
843 pub async fn reject(&self, message_id: &str) -> Result<(), MqError> {
847 self.queue.reject(message_id).await
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854 use std::sync::atomic::{AtomicU32, Ordering};
855
856 #[tokio::test]
861 async fn test_in_memory_queue_basic() {
862 let queue = InMemoryQueue::new();
863 queue.publish("topic1", b"hello").await.unwrap();
864 let msg = queue
865 .consume("topic1")
866 .await
867 .unwrap()
868 .expect("msg should exist");
869 assert_eq!(msg.payload, b"hello");
870 queue.ack(&msg.id).await.unwrap();
871 }
872
873 #[tokio::test]
875 async fn test_l2_next_id_overflow_protection() {
876 let queue = InMemoryQueue::new();
877 {
878 let mut inner = queue.inner.write().await;
879 inner.next_id = u64::MAX;
880 }
881 let result = queue.publish("topic1", b"msg").await;
882 assert!(result.is_err());
883 match result {
884 Err(MqError::Publish(msg)) => {
885 assert!(
886 msg.contains("overflow"),
887 "expected overflow error, got: {}",
888 msg
889 );
890 }
891 _ => panic!("Expected MqError::Publish with overflow message"),
892 }
893 }
894
895 #[tokio::test]
897 async fn test_l2_next_id_near_max() {
898 let queue = InMemoryQueue::new();
899 {
900 let mut inner = queue.inner.write().await;
901 inner.next_id = u64::MAX - 1;
902 }
903 let result1 = queue.publish("topic1", b"msg1").await;
904 assert!(result1.is_ok());
905 let result2 = queue.publish("topic1", b"msg2").await;
906 assert!(result2.is_err());
907 }
908
909 #[tokio::test]
915 async fn test_nack_requeues_message_with_retry_count() {
916 let queue = InMemoryQueue::new();
917 queue.publish("topic", b"msg1").await.unwrap();
918 let msg = queue.consume("topic").await.unwrap().unwrap();
919 assert_eq!(msg.retry_count, 0);
920
921 queue.nack(&msg.id).await.unwrap();
923 assert_eq!(queue.message_count("topic").await, 1);
924 assert_eq!(queue.in_flight_count().await, 0);
925
926 let msg2 = queue.consume("topic").await.unwrap().unwrap();
928 assert_eq!(msg2.id, msg.id);
929 assert_eq!(msg2.retry_count, 1);
930 }
931
932 #[tokio::test]
941 async fn test_nack_increments_retry_count() {
942 let queue = InMemoryQueue::with_max_retries(10);
943 queue.publish("topic", b"data").await.unwrap();
944
945 for expected_retry in 0..5u32 {
946 let msg = queue.consume("topic").await.unwrap().unwrap();
947 assert_eq!(
948 msg.retry_count, expected_retry,
949 "consume should show retry_count before this iteration's nack"
950 );
951 queue.nack(&msg.id).await.unwrap();
952 }
953 assert_eq!(queue.message_count("topic").await, 1);
955 assert_eq!(queue.dead_letter_count("topic").await, 0);
956 }
957
958 #[tokio::test]
960 async fn test_nack_max_retries_sends_to_dlx() {
961 let queue = InMemoryQueue::with_max_retries(3);
963 queue.publish("topic", b"payload").await.unwrap();
964
965 let msg = queue.consume("topic").await.unwrap().unwrap();
967 queue.nack(&msg.id).await.unwrap();
968 assert_eq!(queue.message_count("topic").await, 1);
969 assert_eq!(queue.dead_letter_count("topic").await, 0);
970
971 let msg = queue.consume("topic").await.unwrap().unwrap();
973 assert_eq!(msg.retry_count, 1);
974 queue.nack(&msg.id).await.unwrap();
975 assert_eq!(queue.message_count("topic").await, 1);
976 assert_eq!(queue.dead_letter_count("topic").await, 0);
977
978 let msg = queue.consume("topic").await.unwrap().unwrap();
980 assert_eq!(msg.retry_count, 2);
981 queue.nack(&msg.id).await.unwrap();
982 assert_eq!(queue.message_count("topic").await, 0);
983 assert_eq!(queue.dead_letter_count("topic").await, 1);
984 }
985
986 #[tokio::test]
988 async fn test_nack_max_retries_zero_sends_to_dlx_immediately() {
989 let queue = InMemoryQueue::with_max_retries(0);
990 queue.publish("topic", b"msg").await.unwrap();
991 let msg = queue.consume("topic").await.unwrap().unwrap();
992 assert_eq!(msg.retry_count, 0);
993
994 queue.nack(&msg.id).await.unwrap();
996
997 assert_eq!(queue.message_count("topic").await, 0);
998 assert_eq!(queue.dead_letter_count("topic").await, 1);
999
1000 let dlq_msg = queue
1002 .consume_dead_letter("topic")
1003 .await
1004 .expect("should have dead letter");
1005 assert_eq!(dlq_msg.retry_count, 1);
1006 }
1007
1008 #[tokio::test]
1010 async fn test_nack_unknown_message_id_returns_error() {
1011 let queue = InMemoryQueue::new();
1012 let result = queue.nack("nonexistent-id").await;
1013 assert!(result.is_err());
1014 match result {
1015 Err(MqError::NotSupported(msg)) => {
1016 assert!(msg.contains("not found for nack"));
1017 }
1018 _ => panic!("Expected MqError::NotSupported"),
1019 }
1020 }
1021
1022 #[tokio::test]
1024 async fn test_reject_sends_to_dead_letter_queue() {
1025 let queue = InMemoryQueue::new();
1026 queue.publish("topic", b"bad-msg").await.unwrap();
1027 let msg = queue.consume("topic").await.unwrap().unwrap();
1028
1029 queue.reject(&msg.id).await.unwrap();
1030
1031 assert_eq!(queue.message_count("topic").await, 0);
1032 assert_eq!(queue.in_flight_count().await, 0);
1033 assert_eq!(queue.dead_letter_count("topic").await, 1);
1034 }
1035
1036 #[tokio::test]
1038 async fn test_reject_does_not_increment_retry_count() {
1039 let queue = InMemoryQueue::new();
1040 queue.publish("topic", b"msg").await.unwrap();
1041 let msg = queue.consume("topic").await.unwrap().unwrap();
1042 assert_eq!(msg.retry_count, 0);
1043
1044 queue.reject(&msg.id).await.unwrap();
1045
1046 let dlq_msg = queue
1047 .consume_dead_letter("topic")
1048 .await
1049 .expect("should have dead letter");
1050 assert_eq!(
1051 dlq_msg.retry_count, 0,
1052 "reject should not increment retry_count"
1053 );
1054 }
1055
1056 #[tokio::test]
1058 async fn test_reject_unknown_message_id_returns_error() {
1059 let queue = InMemoryQueue::new();
1060 let result = queue.reject("nonexistent-id").await;
1061 assert!(result.is_err());
1062 }
1063
1064 #[tokio::test]
1066 async fn test_reject_empty_in_flight_returns_error() {
1067 let queue = InMemoryQueue::new();
1068 let result = queue.reject("any-id").await;
1070 assert!(result.is_err());
1071 assert_eq!(queue.dead_letter_count("topic").await, 0);
1072 }
1073
1074 #[tokio::test]
1076 async fn test_dead_letter_count_empty_topic() {
1077 let queue = InMemoryQueue::new();
1078 assert_eq!(queue.dead_letter_count("no-such-topic").await, 0);
1079 }
1080
1081 #[tokio::test]
1083 async fn test_dead_letter_count_after_reject() {
1084 let queue = InMemoryQueue::new();
1085 queue.publish("topic", b"m1").await.unwrap();
1086 queue.publish("topic", b"m2").await.unwrap();
1087
1088 let m1 = queue.consume("topic").await.unwrap().unwrap();
1089 queue.reject(&m1.id).await.unwrap();
1090 assert_eq!(queue.dead_letter_count("topic").await, 1);
1091
1092 let m2 = queue.consume("topic").await.unwrap().unwrap();
1093 queue.reject(&m2.id).await.unwrap();
1094 assert_eq!(queue.dead_letter_count("topic").await, 2);
1095 }
1096
1097 #[tokio::test]
1099 async fn test_consume_dead_letter() {
1100 let queue = InMemoryQueue::new();
1101 queue.publish("topic", b"first").await.unwrap();
1102 queue.publish("topic", b"second").await.unwrap();
1103
1104 let m1 = queue.consume("topic").await.unwrap().unwrap();
1105 queue.reject(&m1.id).await.unwrap();
1106 let m2 = queue.consume("topic").await.unwrap().unwrap();
1107 queue.reject(&m2.id).await.unwrap();
1108
1109 let d1 = queue
1111 .consume_dead_letter("topic")
1112 .await
1113 .expect("should have dead letter");
1114 assert_eq!(d1.payload, b"first");
1115 let d2 = queue
1116 .consume_dead_letter("topic")
1117 .await
1118 .expect("should have dead letter");
1119 assert_eq!(d2.payload, b"second");
1120
1121 assert!(queue.consume_dead_letter("topic").await.is_none());
1123 }
1124
1125 #[tokio::test]
1127 async fn test_consume_dead_letter_empty() {
1128 let queue = InMemoryQueue::new();
1129 assert!(queue.consume_dead_letter("no-such-topic").await.is_none());
1130 }
1131
1132 #[tokio::test]
1134 async fn test_requeue_dead_letter_resets_retry_count() {
1135 let queue = InMemoryQueue::with_max_retries(2);
1136 queue.publish("topic", b"msg").await.unwrap();
1137
1138 let m1 = queue.consume("topic").await.unwrap().unwrap();
1140 queue.nack(&m1.id).await.unwrap();
1141 let m2 = queue.consume("topic").await.unwrap().unwrap();
1143 assert_eq!(m2.retry_count, 1);
1144 queue.nack(&m2.id).await.unwrap();
1145
1146 assert_eq!(queue.dead_letter_count("topic").await, 1);
1147 assert_eq!(queue.message_count("topic").await, 0);
1148
1149 queue.requeue_dead_letter(&m1.id).await.unwrap();
1151 assert_eq!(queue.dead_letter_count("topic").await, 0);
1152 assert_eq!(queue.message_count("topic").await, 1);
1153
1154 let m3 = queue.consume("topic").await.unwrap().unwrap();
1156 assert_eq!(m3.id, m1.id);
1157 assert_eq!(
1158 m3.retry_count, 0,
1159 "retry_count should be reset after requeue"
1160 );
1161 }
1162
1163 #[tokio::test]
1165 async fn test_requeue_dead_letter_not_found() {
1166 let queue = InMemoryQueue::new();
1167 let result = queue.requeue_dead_letter("nonexistent-id").await;
1168 assert!(result.is_err());
1169 match result {
1170 Err(MqError::NotSupported(msg)) => {
1171 assert!(msg.contains("Dead letter not found"));
1172 }
1173 _ => panic!("Expected MqError::NotSupported"),
1174 }
1175 }
1176
1177 #[test]
1183 fn test_reconnect_policy_default_values() {
1184 let policy = ReconnectPolicy::default();
1185 assert_eq!(policy.max_retries, 5);
1186 assert_eq!(policy.initial_delay_ms, 100);
1187 assert_eq!(policy.max_delay_ms, 10_000);
1188 assert!((policy.multiplier - 2.0).abs() < f64::EPSILON);
1189 }
1190
1191 #[test]
1193 fn test_reconnect_policy_next_delay_exponential() {
1194 let policy = ReconnectPolicy {
1195 max_retries: 5,
1196 initial_delay_ms: 100,
1197 max_delay_ms: 10_000,
1198 multiplier: 2.0,
1199 };
1200 assert_eq!(policy.next_delay(0), Duration::from_millis(100));
1202 assert_eq!(policy.next_delay(1), Duration::from_millis(200));
1204 assert_eq!(policy.next_delay(2), Duration::from_millis(400));
1206 assert_eq!(policy.next_delay(3), Duration::from_millis(800));
1208 }
1209
1210 #[test]
1212 fn test_reconnect_policy_next_delay_capped_at_max() {
1213 let policy = ReconnectPolicy {
1214 max_retries: 10,
1215 initial_delay_ms: 100,
1216 max_delay_ms: 1000,
1217 multiplier: 2.0,
1218 };
1219 assert_eq!(policy.next_delay(4), Duration::from_millis(1000));
1221 assert_eq!(policy.next_delay(10), Duration::from_millis(1000));
1223 }
1224
1225 #[test]
1227 fn test_reconnect_policy_zero_attempt() {
1228 let policy = ReconnectPolicy {
1229 max_retries: 3,
1230 initial_delay_ms: 500,
1231 max_delay_ms: 10_000,
1232 multiplier: 3.0,
1233 };
1234 assert_eq!(policy.next_delay(0), Duration::from_millis(500));
1235 }
1236
1237 struct FailingQueue {
1245 call_count: AtomicU32,
1246 succeed_on_attempt: u32,
1247 }
1248
1249 impl FailingQueue {
1250 fn new(succeed_on_attempt: u32) -> Self {
1251 Self {
1252 call_count: AtomicU32::new(0),
1253 succeed_on_attempt,
1254 }
1255 }
1256 }
1257
1258 #[async_trait]
1259 impl MessageQueue for FailingQueue {
1260 async fn publish(&self, _topic: &str, _message: &[u8]) -> Result<(), MqError> {
1261 let attempt = self.call_count.fetch_add(1, Ordering::SeqCst) + 1;
1262 if attempt >= self.succeed_on_attempt {
1263 Ok(())
1264 } else {
1265 Err(MqError::Connection(
1266 "simulated connection error".to_string(),
1267 ))
1268 }
1269 }
1270
1271 async fn consume(&self, _topic: &str) -> Result<Option<Message>, MqError> {
1272 Err(MqError::Connection(
1273 "simulated connection error".to_string(),
1274 ))
1275 }
1276
1277 async fn ack(&self, _message_id: &str) -> Result<(), MqError> {
1278 Err(MqError::Connection("simulated".to_string()))
1279 }
1280
1281 async fn subscribe(&self, _topic: &str) -> Result<(), MqError> {
1282 Err(MqError::Connection("simulated".to_string()))
1283 }
1284 }
1285
1286 struct PublishErrorQueue;
1288
1289 #[async_trait]
1290 impl MessageQueue for PublishErrorQueue {
1291 async fn publish(&self, _topic: &str, _message: &[u8]) -> Result<(), MqError> {
1292 Err(MqError::Publish("non-connection error".to_string()))
1293 }
1294
1295 async fn consume(&self, _topic: &str) -> Result<Option<Message>, MqError> {
1296 Err(MqError::Publish("non-connection error".to_string()))
1297 }
1298
1299 async fn ack(&self, _message_id: &str) -> Result<(), MqError> {
1300 Ok(())
1301 }
1302
1303 async fn subscribe(&self, _topic: &str) -> Result<(), MqError> {
1304 Ok(())
1305 }
1306 }
1307
1308 #[tokio::test]
1310 async fn test_reconnect_retries_on_connection_error() {
1311 let failing = FailingQueue::new(3);
1313 let wrapper = QueueWrapper::with_queue(Box::new(failing)).with_reconnect(ReconnectPolicy {
1314 max_retries: 5,
1315 initial_delay_ms: 1, max_delay_ms: 10,
1317 multiplier: 2.0,
1318 });
1319
1320 let result = wrapper.publish("topic", b"data").await;
1321 assert!(result.is_ok(), "should succeed after retries");
1322 }
1323
1324 #[tokio::test]
1326 async fn test_reconnect_gives_up_after_max_retries() {
1327 let failing = FailingQueue::new(u32::MAX);
1329 let wrapper = QueueWrapper::with_queue(Box::new(failing)).with_reconnect(ReconnectPolicy {
1330 max_retries: 2,
1331 initial_delay_ms: 1,
1332 max_delay_ms: 10,
1333 multiplier: 2.0,
1334 });
1335
1336 let result = wrapper.publish("topic", b"data").await;
1337 assert!(result.is_err());
1338 match result {
1339 Err(MqError::Connection(_)) => {}
1340 _ => panic!("Expected MqError::Connection"),
1341 }
1342 }
1343
1344 #[tokio::test]
1346 async fn test_reconnect_no_retry_on_non_connection_error() {
1347 let wrapper =
1348 QueueWrapper::with_queue(Box::new(PublishErrorQueue)).with_reconnect(ReconnectPolicy {
1349 max_retries: 5,
1350 initial_delay_ms: 1,
1351 max_delay_ms: 10,
1352 multiplier: 2.0,
1353 });
1354
1355 let result = wrapper.publish("topic", b"data").await;
1356 assert!(result.is_err());
1357 match result {
1358 Err(MqError::Publish(msg)) => {
1359 assert!(msg.contains("non-connection error"));
1360 }
1361 _ => panic!("Expected MqError::Publish"),
1362 }
1363 }
1364
1365 #[tokio::test]
1371 async fn test_backpressure_drop_oldest() {
1372 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1373 max_queue_size: 2,
1374 on_overflow: OverflowStrategy::DropOldest,
1375 });
1376
1377 queue.publish("topic", b"m1").await.unwrap();
1378 queue.publish("topic", b"m2").await.unwrap();
1379 queue.publish("topic", b"m3").await.unwrap();
1381
1382 assert_eq!(queue.message_count("topic").await, 2);
1383
1384 let m1 = queue.consume("topic").await.unwrap().unwrap();
1386 assert_eq!(m1.payload, b"m2", "oldest should be dropped");
1387 let m2 = queue.consume("topic").await.unwrap().unwrap();
1388 assert_eq!(m2.payload, b"m3");
1389 }
1390
1391 #[tokio::test]
1393 async fn test_backpressure_drop_newest() {
1394 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1395 max_queue_size: 1,
1396 on_overflow: OverflowStrategy::DropNewest,
1397 });
1398
1399 queue.publish("topic", b"m1").await.unwrap();
1400 let result = queue.publish("topic", b"m2").await;
1402 assert!(result.is_ok());
1403
1404 assert_eq!(queue.message_count("topic").await, 1);
1405 let m = queue.consume("topic").await.unwrap().unwrap();
1407 assert_eq!(m.payload, b"m1", "newest should be dropped");
1408 }
1409
1410 #[tokio::test]
1412 async fn test_backpressure_reject() {
1413 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1414 max_queue_size: 1,
1415 on_overflow: OverflowStrategy::Reject,
1416 });
1417
1418 queue.publish("topic", b"m1").await.unwrap();
1419 let result = queue.publish("topic", b"m2").await;
1421 assert!(result.is_err());
1422
1423 assert_eq!(queue.message_count("topic").await, 1);
1424 }
1425
1426 #[tokio::test]
1428 async fn test_backpressure_block_unblocks_on_consume() {
1429 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1430 max_queue_size: 1,
1431 on_overflow: OverflowStrategy::Block,
1432 });
1433 queue.publish("topic", b"m1").await.unwrap();
1434
1435 let queue_clone = queue.clone();
1437 let handle = tokio::spawn(async move { queue_clone.publish("topic", b"m2").await });
1438
1439 tokio::time::sleep(Duration::from_millis(50)).await;
1441 assert!(!handle.is_finished(), "publish should be blocked");
1442
1443 queue.consume("topic").await.unwrap();
1445
1446 let result = tokio::time::timeout(Duration::from_secs(1), handle)
1448 .await
1449 .expect("publish should complete after consume");
1450 assert!(result.is_ok(), "publish should succeed: {:?}", result);
1451
1452 assert_eq!(queue.message_count("topic").await, 1);
1454 let m = queue.consume("topic").await.unwrap().unwrap();
1455 assert_eq!(m.payload, b"m2");
1456 }
1457
1458 #[tokio::test]
1460 async fn test_backpressure_block_times_out_when_queue_stays_full() {
1461 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1462 max_queue_size: 1,
1463 on_overflow: OverflowStrategy::Block,
1464 });
1465 queue.publish("topic", b"m1").await.unwrap();
1466
1467 let result =
1469 tokio::time::timeout(Duration::from_millis(100), queue.publish("topic", b"m2")).await;
1470
1471 assert!(result.is_err(), "publish should block and time out");
1473
1474 assert_eq!(queue.message_count("topic").await, 1);
1476 }
1477
1478 #[tokio::test]
1480 async fn test_backpressure_block_isolated_per_topic() {
1481 let queue = InMemoryQueue::with_backpressure(BackpressurePolicy {
1482 max_queue_size: 1,
1483 on_overflow: OverflowStrategy::Block,
1484 });
1485 queue.publish("topic-a", b"a1").await.unwrap();
1486 queue.publish("topic-b", b"b1").await.unwrap();
1487
1488 let queue_clone = queue.clone();
1491 let handle = tokio::spawn(async move { queue_clone.publish("topic-a", b"a2").await });
1492
1493 tokio::time::sleep(Duration::from_millis(50)).await;
1494 assert!(!handle.is_finished(), "topic-a publish should be blocked");
1495
1496 queue.consume("topic-b").await.unwrap();
1498 tokio::time::sleep(Duration::from_millis(50)).await;
1499 assert!(
1500 !handle.is_finished(),
1501 "topic-a publish should still be blocked after topic-b consume"
1502 );
1503
1504 queue.consume("topic-a").await.unwrap();
1506 let result = tokio::time::timeout(Duration::from_secs(1), handle)
1507 .await
1508 .expect("publish should complete after topic-a consume");
1509 assert!(result.is_ok());
1510 }
1511}