1pub use crate::errors::{ConsumerError, HandlerError, PublisherError};
7pub use crate::outcomes::{Handled, Received, ReceivedBatch, Sent, SentBatch};
8use crate::CanonicalMessage;
9use anyhow::anyhow;
10use async_trait::async_trait;
11pub use futures::future::BoxFuture;
12use std::any::Any;
13use std::sync::Arc;
14use tracing::warn;
15
16#[derive(Default, Debug, Clone)]
21#[allow(clippy::large_enum_variant)]
22pub enum MessageDisposition {
23 #[default]
25 Ack,
26 Reply(CanonicalMessage),
28 Nack,
30}
31
32impl From<Option<CanonicalMessage>> for MessageDisposition {
33 fn from(opt: Option<CanonicalMessage>) -> Self {
34 match opt {
35 Some(msg) => MessageDisposition::Reply(msg),
36 None => MessageDisposition::Ack,
37 }
38 }
39}
40
41impl From<Handled> for MessageDisposition {
42 fn from(handled: Handled) -> Self {
43 match handled {
44 Handled::Ack => MessageDisposition::Ack,
45 Handled::Publish(msg) => MessageDisposition::Reply(msg),
46 }
47 }
48}
49
50#[async_trait]
55pub trait Handler: Send + Sync + 'static {
56 async fn handle(&self, msg: CanonicalMessage) -> Result<Handled, HandlerError>;
57
58 async fn handle_many(&self, msgs: Vec<CanonicalMessage>) -> Vec<Result<Handled, HandlerError>> {
59 let mut results = Vec::with_capacity(msgs.len());
60 let mut remaining = msgs.len();
61 for msg in msgs {
62 remaining -= 1;
63 let result = self.handle(msg).await;
64 let aborted = match &result {
65 Err(HandlerError::Retryable(_)) => Some("retryable"),
66 Err(HandlerError::Connection(_)) => Some("connection"),
67 Err(HandlerError::NonRetryable(_)) => Some("non-retryable"),
68 Ok(_) => None,
69 };
70 results.push(result);
71 if let Some(kind) = aborted {
72 for _ in 0..remaining {
73 results.push(Err(match kind {
74 "retryable" => HandlerError::Retryable(anyhow!(
75 "batch aborted after earlier retryable handler failure"
76 )),
77 "connection" => HandlerError::Connection(anyhow!(
78 "batch aborted after earlier handler connection failure"
79 )),
80 _ => HandlerError::NonRetryable(anyhow!(
81 "batch aborted after earlier non-retryable handler failure"
82 )),
83 }));
84 }
85 break;
86 }
87 }
88 results
89 }
90
91 fn register_handler(
94 &self,
95 _type_name: &str,
96 _handler: Arc<dyn Handler>,
97 ) -> Option<Arc<dyn Handler>> {
98 None
99 }
100}
101
102#[async_trait]
103impl<T: Handler + ?Sized> Handler for Arc<T> {
104 async fn handle(&self, msg: CanonicalMessage) -> Result<Handled, HandlerError> {
105 (**self).handle(msg).await
106 }
107
108 async fn handle_many(&self, msgs: Vec<CanonicalMessage>) -> Vec<Result<Handled, HandlerError>> {
109 (**self).handle_many(msgs).await
110 }
111
112 fn register_handler(
113 &self,
114 type_name: &str,
115 handler: Arc<dyn Handler>,
116 ) -> Option<Arc<dyn Handler>> {
117 (**self).register_handler(type_name, handler)
118 }
119}
120
121pub trait AsyncHandler: Send + Sync + 'static {
126 fn handle<'a>(&'a self, msg: CanonicalMessage) -> BoxFuture<'a, Result<Handled, HandlerError>>;
127}
128
129pub struct SimpleHandler<T>(pub T);
131
132#[async_trait]
133impl<T: AsyncHandler> Handler for SimpleHandler<T> {
134 async fn handle(&self, msg: CanonicalMessage) -> Result<Handled, HandlerError> {
135 self.0.handle(msg).await
136 }
137}
138
139pub type CommitFunc =
142 Box<dyn FnOnce(MessageDisposition) -> BoxFuture<'static, anyhow::Result<()>> + Send + 'static>;
143
144pub type BatchCommitFunc = Box<
146 dyn FnOnce(Vec<MessageDisposition>) -> BoxFuture<'static, anyhow::Result<()>> + Send + 'static,
147>;
148
149#[derive(Debug, Clone, serde::Serialize)]
151pub struct EndpointStatus {
152 pub healthy: bool,
153 pub target: String,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub pending: Option<usize>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub capacity: Option<usize>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub error: Option<String>,
160 pub details: serde_json::Value,
161}
162impl Default for EndpointStatus {
163 fn default() -> Self {
164 Self {
165 healthy: true,
166 target: String::new(),
167 pending: None,
168 capacity: None,
169 error: None,
170 details: serde_json::Value::Null,
171 }
172 }
173}
174
175pub(crate) fn drain_idle_timeout() -> std::time::Duration {
185 static V: std::sync::OnceLock<std::time::Duration> = std::sync::OnceLock::new();
186 *V.get_or_init(|| {
187 std::env::var("MQ_BRIDGE_DRAIN_IDLE_TIMEOUT_MS")
188 .ok()
189 .and_then(|s| s.parse::<u64>().ok())
190 .map(std::time::Duration::from_millis)
191 .unwrap_or(std::time::Duration::from_millis(1000))
192 })
193}
194
195pub(crate) async fn drain_gated<F: std::future::Future>(
199 exit_on_empty: bool,
200 fut: F,
201) -> Option<F::Output> {
202 if exit_on_empty {
203 tokio::time::timeout(drain_idle_timeout(), fut).await.ok()
204 } else {
205 Some(fut.await)
206 }
207}
208
209#[async_trait]
210pub trait MessageConsumer: Send + Sync {
211 fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
230 None
231 }
232
233 fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
238 None
239 }
240
241 async fn receive_batch(&mut self, _max_messages: usize)
246 -> Result<ReceivedBatch, ConsumerError>;
247
248 async fn receive(&mut self) -> Result<Received, ConsumerError> {
250 loop {
253 let mut batch = self.receive_batch(1).await?;
254 if let Some(msg) = batch.messages.pop() {
255 debug_assert!(batch.messages.is_empty());
256 if !batch.messages.is_empty() {
257 tracing::error!(
258 "receive_batch(1) returned {} extra messages; dropping them (implementation bug)",
259 batch.messages.len()
260 );
261 }
262 return Ok(Received {
263 message: msg,
264 commit: into_commit_func(batch.commit),
265 });
266 }
267 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
269 tokio::task::yield_now().await;
270 }
271 }
272
273 async fn receive_batch_helper(
274 &mut self,
275 _max_messages: usize,
276 ) -> Result<ReceivedBatch, ConsumerError> {
277 let received = self.receive().await?; let batch_commit = Box::new(move |dispositions: Vec<MessageDisposition>| {
279 let single_disposition = dispositions
281 .into_iter()
282 .next()
283 .unwrap_or(MessageDisposition::Ack);
284 (received.commit)(single_disposition)
285 }) as BatchCommitFunc;
286 Ok(ReceivedBatch {
287 messages: vec![received.message],
288 commit: batch_commit,
289 })
290 }
291
292 fn set_exit_on_empty(&mut self, _exit_on_empty: bool) {}
299
300 fn commit_requires_order(&self) -> bool {
314 true
315 }
316
317 async fn status(&self) -> EndpointStatus {
318 EndpointStatus {
319 healthy: true,
320 ..Default::default()
321 }
322 }
323
324 async fn close(&mut self) -> anyhow::Result<()> {
332 if let Some(hook) = self.on_disconnect_hook() {
333 hook.await?;
334 }
335 Ok(())
336 }
337
338 fn as_any(&self) -> &dyn Any;
339}
340
341#[async_trait]
342pub trait MessagePublisher: Send + Sync + 'static {
343 fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
375 None
376 }
377
378 fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
383 None
384 }
385
386 async fn send_batch(
391 &self,
392 messages: Vec<CanonicalMessage>,
393 ) -> Result<SentBatch, PublisherError>;
394
395 async fn send(&self, message: CanonicalMessage) -> Result<Sent, PublisherError> {
396 let message_id = message.message_id;
397 let expects_reply = message.metadata.contains_key("reply_to");
398 match self.send_batch(vec![message]).await {
399 Ok(SentBatch::Ack) => {
400 if expects_reply {
401 warn!("Message {:032x} expected a reply (reply_to set), but publisher returned Ack. Response loop might be broken.", message_id);
402 }
403 Ok(Sent::Ack)
404 }
405 Ok(SentBatch::Partial {
406 mut responses,
407 mut failed,
408 }) => {
409 if let Some((_, err)) = failed.pop() {
410 Err(err)
411 } else if let Some(res) = responses.as_mut().and_then(|r| r.pop()) {
412 Ok(Sent::Response(res))
413 } else {
414 if expects_reply {
415 warn!("Message {:032x} expected a reply (reply_to set), but publisher returned Ack. Response loop might be broken.", message_id);
416 }
417 Ok(Sent::Ack)
418 }
419 }
420 Err(e) => Err(e),
421 }
422 }
423
424 async fn flush(&self) -> anyhow::Result<()> {
425 Ok(())
426 }
427
428 async fn status(&self) -> EndpointStatus {
429 EndpointStatus {
430 healthy: true,
431 ..Default::default()
432 }
433 }
434 fn as_any(&self) -> &dyn Any;
435}
436
437#[async_trait]
438impl<T: MessagePublisher + ?Sized> MessagePublisher for Arc<T> {
439 fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
440 (**self).on_connect_hook()
441 }
442
443 fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
444 (**self).on_disconnect_hook()
445 }
446
447 async fn send(&self, message: CanonicalMessage) -> Result<Sent, PublisherError> {
448 (**self).send(message).await
449 }
450
451 async fn send_batch(
452 &self,
453 messages: Vec<CanonicalMessage>,
454 ) -> Result<SentBatch, PublisherError> {
455 (**self).send_batch(messages).await
456 }
457
458 async fn flush(&self) -> anyhow::Result<()> {
459 (**self).flush().await
460 }
461
462 async fn status(&self) -> EndpointStatus {
463 (**self).status().await
464 }
465
466 fn as_any(&self) -> &dyn Any {
467 (**self).as_any()
468 }
469}
470
471#[async_trait]
472impl<T: MessagePublisher + ?Sized> MessagePublisher for Box<T> {
473 fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
474 (**self).on_connect_hook()
475 }
476
477 fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
478 (**self).on_disconnect_hook()
479 }
480
481 async fn send(&self, message: CanonicalMessage) -> Result<Sent, PublisherError> {
482 (**self).send(message).await
483 }
484
485 async fn send_batch(
486 &self,
487 messages: Vec<CanonicalMessage>,
488 ) -> Result<SentBatch, PublisherError> {
489 (**self).send_batch(messages).await
490 }
491
492 async fn flush(&self) -> anyhow::Result<()> {
493 (**self).flush().await
494 }
495
496 async fn status(&self) -> EndpointStatus {
497 (**self).status().await
498 }
499
500 fn as_any(&self) -> &dyn Any {
501 (**self).as_any()
502 }
503}
504
505#[async_trait]
507pub trait CustomEndpointFactory: Send + Sync + std::fmt::Debug {
508 async fn create_consumer(
509 &self,
510 _route_name: &str,
511 _config: &serde_json::Value,
512 ) -> anyhow::Result<Box<dyn MessageConsumer>> {
513 Err(anyhow::anyhow!(
514 "This custom endpoint does not support creating consumers"
515 ))
516 }
517 async fn create_publisher(
518 &self,
519 _route_name: &str,
520 _config: &serde_json::Value,
521 ) -> anyhow::Result<Box<dyn MessagePublisher>> {
522 Err(anyhow::anyhow!(
523 "This custom endpoint does not support creating publishers"
524 ))
525 }
526}
527
528#[async_trait]
530pub trait CustomMiddlewareFactory: Send + Sync + std::fmt::Debug {
531 async fn apply_consumer(
532 &self,
533 consumer: Box<dyn MessageConsumer>,
534 _route_name: &str,
535 _config: &serde_json::Value,
536 ) -> anyhow::Result<Box<dyn MessageConsumer>> {
537 Ok(consumer)
538 }
539
540 async fn apply_publisher(
541 &self,
542 publisher: Box<dyn MessagePublisher>,
543 _route_name: &str,
544 _config: &serde_json::Value,
545 ) -> anyhow::Result<Box<dyn MessagePublisher>> {
546 Ok(publisher)
547 }
548}
549
550pub const SEND_BATCH_CONCURRENCY: usize = 128;
554
555pub async fn send_batch_helper<P: MessagePublisher + ?Sized>(
565 publisher: &P,
566 messages: Vec<CanonicalMessage>,
567 callback: impl for<'a> Fn(&'a P, CanonicalMessage) -> BoxFuture<'a, Result<Sent, PublisherError>>
568 + Send
569 + Sync,
570) -> Result<SentBatch, PublisherError> {
571 use futures::stream::StreamExt;
572
573 let mut responses = Vec::new();
574 let mut failed_messages = Vec::new();
575
576 let callback = &callback;
580 let mut results = futures::stream::iter(messages.into_iter().enumerate().map(
581 |(idx, msg)| async move {
582 let result = callback(publisher, msg.clone()).await;
583 (idx, msg, result)
584 },
585 ))
586 .buffer_unordered(SEND_BATCH_CONCURRENCY);
587
588 while let Some((idx, msg, result)) = results.next().await {
589 match result {
590 Ok(Sent::Response(resp)) => responses.push((idx, resp)),
591 Ok(Sent::Ack) => {}
592 Err(e) => failed_messages.push((idx, msg, e)),
597 }
598 }
599
600 responses.sort_by_key(|(idx, _)| *idx);
601 let responses: Vec<_> = responses.into_iter().map(|(_, resp)| resp).collect();
602 failed_messages.sort_by_key(|(idx, _, _)| *idx);
603 let failed_messages: Vec<_> = failed_messages
604 .into_iter()
605 .map(|(_, msg, err)| (msg, err))
606 .collect();
607
608 if failed_messages.is_empty() && responses.is_empty() {
609 Ok(SentBatch::Ack)
610 } else {
611 Ok(SentBatch::Partial {
612 responses: if responses.is_empty() {
613 None
614 } else {
615 Some(responses)
616 },
617 failed: failed_messages,
618 })
619 }
620}
621
622pub fn into_commit_func(batch_commit: BatchCommitFunc) -> CommitFunc {
626 Box::new(move |disposition: MessageDisposition| {
627 let batch_disposition = vec![disposition];
628 batch_commit(batch_disposition)
629 })
630}
631
632pub fn into_batch_commit_func(commit: CommitFunc) -> BatchCommitFunc {
638 Box::new(move |mut dispositions: Vec<MessageDisposition>| {
639 let single_disposition = if dispositions.len() > 1 {
640 warn!(
641 "into_batch_commit_func called with batch of {} messages; dropping all responses to avoid partial commit (incorrect usage)",
642 dispositions.len()
643 );
644 MessageDisposition::Ack
646 } else {
647 dispositions.pop().unwrap_or(MessageDisposition::Ack)
648 };
649 commit(single_disposition)
650 })
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use crate::CanonicalMessage;
657 use anyhow::anyhow;
658 use std::sync::{
659 atomic::{AtomicUsize, Ordering},
660 Arc,
661 };
662
663 struct MockPublisher;
664 #[async_trait]
665 impl MessagePublisher for MockPublisher {
666 async fn send_batch(
667 &self,
668 _msgs: Vec<CanonicalMessage>,
669 ) -> Result<SentBatch, PublisherError> {
670 Ok(SentBatch::Ack)
671 }
672 fn as_any(&self) -> &dyn Any {
673 self
674 }
675 }
676
677 #[tokio::test]
678 async fn test_send_batch_helper_partial_failure() {
679 let publisher = MockPublisher;
680 let msgs = vec![
681 CanonicalMessage::from("1"),
682 CanonicalMessage::from("2"),
683 CanonicalMessage::from("3"),
684 ];
685
686 let result = send_batch_helper(&publisher, msgs.clone(), |_pub, msg| {
687 Box::pin(async move {
688 let payload = msg.get_payload_str();
689 if payload == "1" {
690 Ok(Sent::Response(CanonicalMessage::from("resp1")))
691 } else if payload == "2" {
692 Err(PublisherError::Retryable(anyhow!("fail")))
693 } else {
694 Ok(Sent::Ack)
695 }
696 })
697 })
698 .await;
699
700 match result {
701 Ok(SentBatch::Partial { responses, failed }) => {
702 assert!(responses.is_some());
703 let resps = responses.unwrap();
704 assert_eq!(resps.len(), 1);
705 assert_eq!(resps[0].get_payload_str(), "resp1");
706
707 assert_eq!(failed.len(), 1);
711 assert_eq!(failed[0].0.get_payload_str(), "2");
712 assert!(matches!(failed[0].1, PublisherError::Retryable(_)));
713 }
714 _ => panic!("Expected Partial result"),
715 }
716 }
717
718 #[tokio::test]
719 async fn test_send_batch_helper_preserves_response_order() {
720 let publisher = MockPublisher;
723 let count = 16u64;
724 let msgs: Vec<CanonicalMessage> = (0..count)
725 .map(|i| CanonicalMessage::from(i.to_string()))
726 .collect();
727
728 let result = send_batch_helper(&publisher, msgs, |_pub, msg| {
729 Box::pin(async move {
730 let i: u64 = msg.get_payload_str().parse().unwrap();
731 tokio::time::sleep(std::time::Duration::from_millis((count - i) * 2)).await;
733 let mut resp = CanonicalMessage::from(msg.get_payload_str().to_string());
734 resp.message_id = msg.message_id;
735 Ok(Sent::Response(resp))
736 })
737 })
738 .await
739 .unwrap();
740
741 match result {
742 SentBatch::Partial { responses, failed } => {
743 assert!(failed.is_empty());
744 let responses = responses.expect("expected responses");
745 let order: Vec<u64> = responses
746 .iter()
747 .map(|r| r.get_payload_str().parse().unwrap())
748 .collect();
749 assert_eq!(
750 order,
751 (0..count).collect::<Vec<u64>>(),
752 "send_batch_helper must preserve input order",
753 );
754 }
755 SentBatch::Ack => panic!("expected per-message responses"),
756 }
757 }
758
759 #[tokio::test]
760 async fn test_send_batch_helper_keeps_pipeline_full_when_early_send_is_slow() {
761 let publisher = Arc::new(MockPublisher);
762 let total = SEND_BATCH_CONCURRENCY + 1;
763 let msgs: Vec<CanonicalMessage> = (0..total)
764 .map(|i| CanonicalMessage::from(i.to_string()))
765 .collect();
766 let started = Arc::new(AtomicUsize::new(0));
767 let all_started = Arc::new(tokio::sync::Notify::new());
768 let release_first = Arc::new(tokio::sync::Notify::new());
769
770 let helper = tokio::spawn({
771 let publisher = Arc::clone(&publisher);
772 let started = Arc::clone(&started);
773 let all_started = Arc::clone(&all_started);
774 let release_first = Arc::clone(&release_first);
775 async move {
776 send_batch_helper(&publisher, msgs, |_pub, msg| {
777 let started = Arc::clone(&started);
778 let all_started = Arc::clone(&all_started);
779 let release_first = Arc::clone(&release_first);
780 Box::pin(async move {
781 let idx: usize = msg.get_payload_str().parse().unwrap();
782 if started.fetch_add(1, Ordering::SeqCst) + 1 == total {
783 all_started.notify_waiters();
784 }
785 if idx == 0 {
786 release_first.notified().await;
787 }
788 let mut resp = CanonicalMessage::from(idx.to_string());
789 resp.message_id = msg.message_id;
790 Ok(Sent::Response(resp))
791 })
792 })
793 .await
794 }
795 });
796
797 tokio::time::timeout(std::time::Duration::from_millis(200), async {
798 loop {
799 let notified = all_started.notified();
802 tokio::pin!(notified);
803 notified.as_mut().enable();
804 if started.load(Ordering::SeqCst) == total {
805 break;
806 }
807 notified.await;
808 }
809 })
810 .await
811 .expect("a completed later send should free a slot even while the first send is blocked");
812
813 release_first.notify_waiters();
814 let result = helper.await.unwrap().unwrap();
815 match result {
816 SentBatch::Partial { responses, failed } => {
817 assert!(failed.is_empty());
818 let order: Vec<usize> = responses
819 .expect("expected responses")
820 .iter()
821 .map(|r| r.get_payload_str().parse().unwrap())
822 .collect();
823 assert_eq!(order, (0..total).collect::<Vec<_>>());
824 }
825 SentBatch::Ack => panic!("expected per-message responses"),
826 }
827 }
828
829 #[tokio::test]
830 async fn test_send_propagates_single_error() {
831 struct FailPublisher;
832 #[async_trait]
833 impl MessagePublisher for FailPublisher {
834 async fn send_batch(
835 &self,
836 msgs: Vec<CanonicalMessage>,
837 ) -> Result<SentBatch, PublisherError> {
838 Ok(SentBatch::Partial {
840 responses: None,
841 failed: vec![(
842 msgs[0].clone(),
843 PublisherError::NonRetryable(anyhow!("inner")),
844 )],
845 })
846 }
847 fn as_any(&self) -> &dyn Any {
848 self
849 }
850 }
851
852 let publ = FailPublisher;
853 let res = publ.send(CanonicalMessage::from("test")).await;
854
855 assert!(res.is_err());
856 match res.unwrap_err() {
857 PublisherError::NonRetryable(e) => assert_eq!(e.to_string(), "inner"),
858 _ => panic!("Expected NonRetryable error"),
859 }
860 }
861
862 #[tokio::test]
863 async fn test_simple_handler_wrapper() {
864 struct MyLogic;
865 impl AsyncHandler for MyLogic {
866 fn handle<'a>(
867 &'a self,
868 _msg: CanonicalMessage,
869 ) -> BoxFuture<'a, Result<Handled, HandlerError>> {
870 Box::pin(async { Ok(Handled::Ack) })
871 }
872 }
873
874 let handler = SimpleHandler(MyLogic);
875 let res = handler.handle(CanonicalMessage::from("test")).await;
876 assert!(matches!(res, Ok(Handled::Ack)));
877 }
878}