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