Skip to main content

mq_bridge/
command_handler.rs

1//  mq-bridge
2//  © Copyright 2025, by Marco Mengelkoch
3//  Licensed under MIT License, see License file for more details
4//  git clone https://github.com/marcomq/mq-bridge
5
6use crate::traits::{BoxFuture, Handler, MessagePublisher};
7use crate::traits::{Handled, HandlerError};
8use crate::CanonicalMessage;
9use async_trait::async_trait;
10use std::any::Any;
11use std::future::Future;
12use std::sync::Arc;
13
14use crate::traits::{PublisherError, Sent, SentBatch};
15#[async_trait]
16impl<F, Fut> Handler for F
17where
18    F: Fn(CanonicalMessage) -> Fut + Send + Sync + 'static,
19    Fut: Future<Output = Result<Handled, HandlerError>> + Send,
20{
21    async fn handle(&self, msg: CanonicalMessage) -> Result<Handled, HandlerError> {
22        self(msg).await
23    }
24}
25
26/// A publisher middleware that intercepts messages and passes them to a `Handler`.
27/// If the handler returns a new message, it is passed to the inner publisher.
28pub struct CommandPublisher {
29    inner: Box<dyn MessagePublisher>,
30    handler: Arc<dyn Handler>,
31}
32
33impl CommandPublisher {
34    pub fn new(inner: impl MessagePublisher, handler: impl Handler + 'static) -> Self {
35        Self {
36            inner: Box::new(inner),
37            handler: Arc::new(handler),
38        }
39    }
40}
41
42#[async_trait]
43impl MessagePublisher for CommandPublisher {
44    fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
45        self.inner.on_connect_hook()
46    }
47
48    fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
49        self.inner.on_disconnect_hook()
50    }
51
52    async fn send(&self, message: CanonicalMessage) -> Result<Sent, PublisherError> {
53        let inbound_correlation_id = message.metadata.get("correlation_id").cloned();
54        let original_id = message.message_id;
55        match self.handler.handle(message).await {
56            Ok(Handled::Publish(mut response_msg)) => {
57                // For internal correlation, set the response message's ID to the original.
58                response_msg.message_id = original_id;
59                // For end-to-end tracing, propagate or create a correlation_id.
60                let fallback_correlation_id =
61                    inbound_correlation_id.unwrap_or_else(|| format!("{:032x}", original_id));
62                response_msg
63                    .metadata
64                    .entry("correlation_id".to_string())
65                    .or_insert(fallback_correlation_id);
66                self.inner.send(response_msg).await
67            }
68            Ok(Handled::Ack) => Ok(Sent::Ack),
69            Err(e) => Err(e), // Converts HandlerError to PublisherError
70        }
71    }
72
73    async fn send_batch(
74        &self,
75        messages: Vec<CanonicalMessage>,
76    ) -> Result<SentBatch, PublisherError> {
77        let handler_results = self.handler.handle_many(messages.clone()).await;
78
79        if handler_results.len() != messages.len() {
80            return Err(PublisherError::NonRetryable(anyhow::anyhow!(
81                "handler returned {} results for {} messages",
82                handler_results.len(),
83                messages.len()
84            )));
85        }
86
87        // Sort handler output into messages to publish vs. already failed/ack'd, then hand
88        // the publishable ones to the inner `send_batch` in one call. Per-message `send()`
89        // here silently defeats batching on any handler route (mq-bridge-app attaches one to
90        // every consumer route), turning a batch_size of 1024 into 1024 publish round trips.
91        let mut to_publish = Vec::with_capacity(messages.len());
92        let mut failed: Vec<(CanonicalMessage, PublisherError)> = Vec::new();
93
94        for (message, result) in messages.into_iter().zip(handler_results) {
95            let original_id = message.message_id;
96            match result {
97                Ok(Handled::Ack) => {}
98                Ok(Handled::Publish(mut response_msg)) => {
99                    let inbound_correlation_id = message.metadata.get("correlation_id").cloned();
100                    response_msg.message_id = original_id;
101                    let fallback_correlation_id =
102                        inbound_correlation_id.unwrap_or_else(|| format!("{:032x}", original_id));
103                    response_msg
104                        .metadata
105                        .entry("correlation_id".to_string())
106                        .or_insert(fallback_correlation_id);
107                    to_publish.push(response_msg);
108                }
109                Err(HandlerError::NonRetryable(err)) => {
110                    failed.push((message, PublisherError::NonRetryable(err)));
111                }
112                Err(HandlerError::Retryable(err)) => {
113                    failed.push((message, PublisherError::Retryable(err)));
114                }
115                Err(HandlerError::Connection(err)) => {
116                    failed.push((message, PublisherError::Connection(err)));
117                }
118            }
119        }
120
121        if to_publish.is_empty() {
122            return if failed.is_empty() {
123                Ok(SentBatch::Ack)
124            } else {
125                Ok(SentBatch::Partial {
126                    responses: None,
127                    failed,
128                })
129            };
130        }
131
132        match self.inner.send_batch(to_publish).await {
133            Ok(SentBatch::Ack) => {
134                if failed.is_empty() {
135                    Ok(SentBatch::Ack)
136                } else {
137                    Ok(SentBatch::Partial {
138                        responses: None,
139                        failed,
140                    })
141                }
142            }
143            Ok(SentBatch::Partial {
144                responses,
145                failed: inner_failed,
146            }) => {
147                failed.extend(inner_failed);
148                Ok(SentBatch::Partial { responses, failed })
149            }
150            // An outright batch send failure is a batch-level error; propagate it
151            // directly rather than synthesizing a per-message failure for each one.
152            Err(err) => Err(err),
153        }
154    }
155
156    async fn flush(&self) -> anyhow::Result<()> {
157        self.inner.flush().await
158    }
159
160    fn as_any(&self) -> &dyn Any {
161        self
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
168
169    use super::*;
170    use crate::endpoints::memory::MemoryPublisher;
171
172    #[tokio::test]
173    async fn test_command_handler_produces_response() {
174        let memory_publisher = MemoryPublisher::new_local("test_command_out_resp", 10);
175        let channel = memory_publisher.channel();
176
177        let handler = |msg: CanonicalMessage| async move {
178            let response_payload = format!("response_to_{}", String::from_utf8_lossy(&msg.payload));
179            Ok(Handled::Publish(response_payload.into()))
180        };
181
182        let publisher = CommandPublisher::new(memory_publisher, handler);
183
184        publisher.send("command1".into()).await.unwrap();
185
186        let received = channel.drain_messages();
187        assert_eq!(received.len(), 1);
188        assert_eq!(received[0].payload, "response_to_command1".as_bytes());
189    }
190
191    #[tokio::test]
192    async fn test_command_handler_acks() {
193        let memory_publisher = MemoryPublisher::new_local("test_command_out_ack", 10);
194        let channel = memory_publisher.channel();
195
196        let handler = |_msg: CanonicalMessage| async move { Ok(Handled::Ack) };
197
198        let publisher = CommandPublisher::new(memory_publisher, handler);
199
200        let result = publisher.send("command1".into()).await.unwrap();
201
202        assert!(matches!(result, Sent::Ack));
203        let received = channel.drain_messages();
204        assert_eq!(received.len(), 0);
205    }
206
207    #[tokio::test]
208    async fn test_command_handler_retryable_error() {
209        let memory_publisher = MemoryPublisher::new_local("test_command_out_err", 10);
210
211        let handler = |_msg: CanonicalMessage| async move {
212            Err(HandlerError::Retryable(anyhow::anyhow!("db is down")))
213        };
214
215        let publisher = CommandPublisher::new(memory_publisher, handler);
216        let result = publisher.send("command1".into()).await;
217
218        assert!(result.is_err());
219        let err = result.unwrap_err();
220        // The HandlerError is converted into a PublisherError
221        assert!(matches!(err, PublisherError::Retryable(_)));
222    }
223
224    #[tokio::test]
225    async fn test_command_handler_integration_with_memory_consumer() {
226        use crate::endpoints::memory::MemoryConsumer;
227        use crate::traits::MessageConsumer;
228
229        let mut consumer = MemoryConsumer::new_local("cmd_input", 10);
230        let input_channel = consumer.channel();
231
232        // Setup Output (MemoryPublisher wrapped by CommandPublisher)
233        let memory_publisher = MemoryPublisher::new_local("cmd_output", 10);
234        let output_channel = memory_publisher.channel();
235
236        // Create Publisher Middleware with inline handler
237        let publisher =
238            CommandPublisher::new(memory_publisher, |msg: CanonicalMessage| async move {
239                let payload = String::from_utf8_lossy(&msg.payload);
240                let response = format!("processed_{}", payload);
241                Ok(Handled::Publish(response.into()))
242            });
243
244        input_channel
245            .send_message("test_data".into())
246            .await
247            .unwrap();
248
249        // Simulate Bridge Loop (Consume -> Publish)
250        let received = consumer.receive().await.unwrap();
251        let result = publisher.send(received.message).await.unwrap();
252
253        assert!(matches!(result, Sent::Ack));
254
255        let output_msgs = output_channel.drain_messages();
256        assert_eq!(output_msgs.len(), 1);
257        assert_eq!(output_msgs[0].payload.to_vec(), b"processed_test_data");
258
259        let _ = (received.commit)(crate::traits::MessageDisposition::Ack).await;
260    }
261
262    #[tokio::test(flavor = "multi_thread")]
263    async fn test_command_handler_with_route_config() {
264        use crate::models::{Endpoint, Route};
265
266        let success = Arc::new(AtomicBool::new(false));
267        let success_clone = success.clone();
268
269        let handler = move |mut msg: CanonicalMessage| {
270            success_clone.store(true, Ordering::SeqCst);
271            msg.set_payload_str(format!("modified {}", msg.get_payload_str()));
272            async move { Ok(Handled::Publish(msg)) }
273        };
274        let route = Route::new(
275            Endpoint::new_memory("route_in", 100),
276            Endpoint::new_memory("route_out", 100),
277        )
278        .with_handler(handler);
279
280        route.deploy("command_handler_test_route").await.unwrap();
281
282        let input_channel = route.input.channel().unwrap();
283        input_channel.send_message("hello".into()).await.unwrap();
284
285        let mut verifier = route.connect_to_output("verifier").await.unwrap();
286        let received = verifier.receive().await.unwrap();
287        assert_eq!(received.message.get_payload_str(), "modified hello");
288        assert!(success.load(Ordering::SeqCst));
289        Route::stop("command_handler_test_route").await;
290    }
291
292    #[tokio::test]
293    async fn test_command_handler_inner_publisher_failure() {
294        use crate::traits::MessagePublisher;
295
296        struct FailPublisher;
297        #[async_trait]
298        impl MessagePublisher for FailPublisher {
299            async fn send(&self, _msg: CanonicalMessage) -> Result<Sent, PublisherError> {
300                Err(PublisherError::NonRetryable(anyhow::anyhow!("inner fail")))
301            }
302            async fn send_batch(
303                &self,
304                _msgs: Vec<CanonicalMessage>,
305            ) -> Result<SentBatch, PublisherError> {
306                Ok(SentBatch::Ack)
307            }
308            fn as_any(&self) -> &dyn std::any::Any {
309                self
310            }
311        }
312
313        let handler = |msg: CanonicalMessage| async move { Ok(Handled::Publish(msg)) };
314        let publisher = CommandPublisher::new(FailPublisher, handler);
315        let result = publisher.send("test".into()).await;
316        assert!(result.is_err());
317        assert!(result.unwrap_err().to_string().contains("inner fail"));
318    }
319
320    #[tokio::test]
321    async fn test_command_handler_preserves_message_id() {
322        let memory_publisher = MemoryPublisher::new_local("test_cmd_id_preservation", 10);
323        let channel = memory_publisher.channel();
324
325        let handler = |_msg: CanonicalMessage| async move {
326            let new_msg = CanonicalMessage::new(b"response".to_vec(), None);
327            Ok(Handled::Publish(new_msg))
328        };
329
330        let publisher = CommandPublisher::new(memory_publisher, handler);
331        let original_id = 987654321u128;
332        publisher
333            .send(CanonicalMessage::new(b"req".to_vec(), Some(original_id)))
334            .await
335            .unwrap();
336
337        let received = channel.drain_messages();
338        assert_eq!(received[0].message_id, original_id);
339    }
340
341    #[tokio::test]
342    async fn test_command_handler_send_batch_uses_handler_handle_many() {
343        struct BatchAwareHandler {
344            single_calls: AtomicUsize,
345            batch_calls: AtomicUsize,
346        }
347
348        #[async_trait]
349        impl Handler for BatchAwareHandler {
350            async fn handle(&self, _msg: CanonicalMessage) -> Result<Handled, HandlerError> {
351                self.single_calls.fetch_add(1, Ordering::SeqCst);
352                Ok(Handled::Ack)
353            }
354
355            async fn handle_many(
356                &self,
357                msgs: Vec<CanonicalMessage>,
358            ) -> Vec<Result<Handled, HandlerError>> {
359                self.batch_calls.fetch_add(1, Ordering::SeqCst);
360                msgs.into_iter()
361                    .map(|mut msg| {
362                        msg.set_payload_str(format!("batched {}", msg.get_payload_str()));
363                        Ok(Handled::Publish(msg))
364                    })
365                    .collect()
366            }
367        }
368
369        let memory_publisher = MemoryPublisher::new_local("test_cmd_batch_many", 10);
370        let channel = memory_publisher.channel();
371        let handler = Arc::new(BatchAwareHandler {
372            single_calls: AtomicUsize::new(0),
373            batch_calls: AtomicUsize::new(0),
374        });
375        let publisher = CommandPublisher::new(memory_publisher, handler.clone());
376
377        let result = publisher
378            .send_batch(vec!["one".into(), "two".into(), "three".into()])
379            .await
380            .unwrap();
381
382        assert!(matches!(result, SentBatch::Ack));
383        assert_eq!(handler.single_calls.load(Ordering::SeqCst), 0);
384        assert_eq!(handler.batch_calls.load(Ordering::SeqCst), 1);
385
386        let received = channel.drain_messages();
387        assert_eq!(received.len(), 3);
388        assert_eq!(received[0].get_payload_str(), "batched one");
389        assert_eq!(received[1].get_payload_str(), "batched two");
390        assert_eq!(received[2].get_payload_str(), "batched three");
391    }
392
393    #[tokio::test]
394    async fn test_command_handler_send_batch_non_retryable_handler_error_continues() {
395        struct PartiallyFailingHandler;
396
397        #[async_trait]
398        impl Handler for PartiallyFailingHandler {
399            async fn handle(&self, _msg: CanonicalMessage) -> Result<Handled, HandlerError> {
400                unreachable!("send_batch should use handle_many")
401            }
402
403            async fn handle_many(
404                &self,
405                msgs: Vec<CanonicalMessage>,
406            ) -> Vec<Result<Handled, HandlerError>> {
407                msgs.into_iter()
408                    .map(|msg| {
409                        if msg.get_payload_str() == "two" {
410                            Err(HandlerError::NonRetryable(anyhow::anyhow!("bad message")))
411                        } else {
412                            Ok(Handled::Publish(msg))
413                        }
414                    })
415                    .collect()
416            }
417        }
418
419        let memory_publisher = MemoryPublisher::new_local("test_cmd_batch_non_retryable", 10);
420        let channel = memory_publisher.channel();
421        let publisher = CommandPublisher::new(memory_publisher, PartiallyFailingHandler);
422
423        let result = publisher
424            .send_batch(vec!["one".into(), "two".into(), "three".into()])
425            .await
426            .unwrap();
427
428        match result {
429            SentBatch::Partial { responses, failed } => {
430                assert!(responses.is_none());
431                assert_eq!(failed.len(), 1);
432                assert_eq!(failed[0].0.get_payload_str(), "two");
433                assert!(matches!(failed[0].1, PublisherError::NonRetryable(_)));
434            }
435            other => panic!("expected partial failure, got {other:?}"),
436        }
437
438        let received = channel.drain_messages();
439        assert_eq!(received.len(), 2);
440        assert_eq!(received[0].get_payload_str(), "one");
441        assert_eq!(received[1].get_payload_str(), "three");
442    }
443
444    #[tokio::test]
445    async fn test_command_handler_send_batch_batches_inner_publish_and_reports_per_message_failures(
446    ) {
447        struct PublishAllHandler;
448
449        #[async_trait]
450        impl Handler for PublishAllHandler {
451            async fn handle(&self, _msg: CanonicalMessage) -> Result<Handled, HandlerError> {
452                unreachable!("send_batch should use handle_many")
453            }
454
455            async fn handle_many(
456                &self,
457                msgs: Vec<CanonicalMessage>,
458            ) -> Vec<Result<Handled, HandlerError>> {
459                msgs.into_iter()
460                    .map(|msg| Ok(Handled::Publish(msg)))
461                    .collect()
462            }
463        }
464
465        // Publish via a single inner `send_batch`, not per-message `send()`, which
466        // silently defeats batching on any handler route.
467        struct RetryableSecondMessagePublisher {
468            batch_calls: Arc<AtomicUsize>,
469        }
470
471        #[async_trait]
472        impl MessagePublisher for RetryableSecondMessagePublisher {
473            async fn send(&self, _msg: CanonicalMessage) -> Result<Sent, PublisherError> {
474                unreachable!("command batch publishing should use a single inner send_batch call")
475            }
476
477            async fn send_batch(
478                &self,
479                msgs: Vec<CanonicalMessage>,
480            ) -> Result<SentBatch, PublisherError> {
481                self.batch_calls.fetch_add(1, Ordering::SeqCst);
482                let failed = msgs
483                    .into_iter()
484                    .filter(|msg| msg.get_payload_str() == "two")
485                    .map(|msg| {
486                        (
487                            msg,
488                            PublisherError::Retryable(anyhow::anyhow!("temporary failure")),
489                        )
490                    })
491                    .collect();
492                Ok(SentBatch::Partial {
493                    responses: None,
494                    failed,
495                })
496            }
497
498            fn as_any(&self) -> &dyn std::any::Any {
499                self
500            }
501        }
502
503        let batch_calls = Arc::new(AtomicUsize::new(0));
504        let publisher = CommandPublisher::new(
505            RetryableSecondMessagePublisher {
506                batch_calls: batch_calls.clone(),
507            },
508            PublishAllHandler,
509        );
510
511        let result = publisher
512            .send_batch(vec!["one".into(), "two".into(), "three".into()])
513            .await
514            .unwrap();
515
516        assert_eq!(
517            batch_calls.load(Ordering::SeqCst),
518            1,
519            "the whole batch should be sent in a single inner send_batch call"
520        );
521        match result {
522            SentBatch::Partial { responses, failed } => {
523                assert!(responses.is_none());
524                assert_eq!(failed.len(), 1);
525                assert_eq!(failed[0].0.get_payload_str(), "two");
526                assert!(matches!(failed[0].1, PublisherError::Retryable(_)));
527            }
528            other => panic!("expected partial failure, got {other:?}"),
529        }
530    }
531}