Skip to main content

mq_bridge/endpoints/
switch.rs

1use crate::traits::{MessagePublisher, PublisherError, Sent, SentBatch};
2use crate::CanonicalMessage;
3use async_trait::async_trait;
4use std::any::Any;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tracing::warn;
8
9pub struct SwitchPublisher {
10    metadata_key: String,
11    cases: HashMap<String, Arc<dyn MessagePublisher>>,
12    default: Option<Arc<dyn MessagePublisher>>,
13}
14
15impl SwitchPublisher {
16    pub fn new(
17        metadata_key: String,
18        cases: HashMap<String, Arc<dyn MessagePublisher>>,
19        default: Option<Arc<dyn MessagePublisher>>,
20    ) -> Self {
21        Self {
22            metadata_key,
23            cases,
24            default,
25        }
26    }
27
28    fn get_publisher(&self, message: &CanonicalMessage) -> Option<&Arc<dyn MessagePublisher>> {
29        if let Some(val) = message.metadata.get(&self.metadata_key) {
30            if let Some(publisher) = self.cases.get(val) {
31                return Some(publisher);
32            }
33        }
34        self.default.as_ref()
35    }
36}
37
38#[async_trait]
39impl MessagePublisher for SwitchPublisher {
40    async fn send(&self, message: CanonicalMessage) -> Result<Sent, PublisherError> {
41        if let Some(publisher) = self.get_publisher(&message) {
42            publisher.send(message).await
43        } else {
44            warn!(
45                "Switch publisher dropped message with id {:032x}: metadata key '{}' not found or no matching case/default.",
46                message.message_id, self.metadata_key
47            );
48            Ok(Sent::Ack)
49        }
50    }
51
52    async fn send_batch(
53        &self,
54        messages: Vec<CanonicalMessage>,
55    ) -> Result<SentBatch, PublisherError> {
56        use futures::future::join_all;
57        use std::collections::HashMap;
58
59        if messages.is_empty() {
60            return Ok(SentBatch::Ack);
61        }
62
63        // Group messages by their target publisher.
64        // We use the raw pointer of the Arc as a key to group messages for the same publisher instance.
65        let mut grouped_messages: HashMap<
66            String,
67            (Arc<dyn MessagePublisher>, Vec<CanonicalMessage>),
68        > = HashMap::new();
69
70        for message in messages {
71            if let Some(publisher) = self.get_publisher(&message) {
72                // Use the pointer address of the Arc as a key. This is safe as the Arcs live
73                // as long as the SwitchPublisher and we clone them into the map.
74                grouped_messages
75                    .entry(
76                        message
77                            .metadata
78                            .get(&self.metadata_key)
79                            .cloned()
80                            .unwrap_or_default(),
81                    )
82                    .or_insert_with(|| (publisher.clone(), Vec::new()))
83                    .1
84                    .push(message);
85            } else {
86                warn!(
87                    "Switch publisher dropped message with id {:032x}: metadata key '{}' not found or no matching case/default.",
88                    message.message_id, self.metadata_key
89                );
90            }
91        }
92
93        // Create futures for sending each group as a batch.
94        let batch_sends = grouped_messages
95            .into_values()
96            .map(|(publisher, batch)| async move { publisher.send_batch(batch).await });
97
98        let results = join_all(batch_sends).await;
99
100        // Aggregate results from all the batch sends.
101        let mut all_responses = Vec::new();
102        let mut all_failed = Vec::new();
103
104        for result in results {
105            match result {
106                Ok(SentBatch::Ack) => {}
107                Ok(SentBatch::Partial { responses, failed }) => {
108                    if let Some(resps) = responses {
109                        all_responses.extend(resps);
110                    }
111                    all_failed.extend(failed);
112                }
113                Err(e) => {
114                    // If a whole sub-batch fails, we can't easily recover the messages that were part of it.
115                    // Propagating the error is the safest and simplest option. The caller (e.g., retry middleware)
116                    // will have to re-process the original, larger batch.
117                    return Err(e);
118                }
119            }
120        }
121
122        if all_failed.is_empty() && all_responses.is_empty() {
123            Ok(SentBatch::Ack)
124        } else {
125            Ok(SentBatch::Partial {
126                responses: if all_responses.is_empty() {
127                    None
128                } else {
129                    Some(all_responses)
130                },
131                failed: all_failed,
132            })
133        }
134    }
135
136    fn as_any(&self) -> &dyn Any {
137        self
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::endpoints::memory::MemoryPublisher;
145    use std::sync::Arc;
146
147    #[tokio::test]
148    async fn test_switch_publisher_routing() {
149        // Setup destinations
150        let pub_a = MemoryPublisher::new_local("topic_a", 10);
151        let pub_b = MemoryPublisher::new_local("topic_b", 10);
152        let pub_default = MemoryPublisher::new_local("topic_default", 10);
153
154        let chan_a = pub_a.channel();
155        let chan_b = pub_b.channel();
156        let chan_default = pub_default.channel();
157
158        let mut cases = HashMap::new();
159        cases.insert(
160            "A".to_string(),
161            Arc::new(pub_a) as Arc<dyn MessagePublisher>,
162        );
163        cases.insert(
164            "B".to_string(),
165            Arc::new(pub_b) as Arc<dyn MessagePublisher>,
166        );
167
168        let switch = SwitchPublisher::new(
169            "route_key".to_string(),
170            cases,
171            Some(Arc::new(pub_default) as Arc<dyn MessagePublisher>),
172        );
173
174        // Test Case A
175        let msg_a = CanonicalMessage::from("payload_a").with_metadata_kv("route_key", "A");
176        switch.send(msg_a).await.unwrap();
177        assert_eq!(chan_a.len(), 1);
178        assert_eq!(chan_b.len(), 0);
179        assert_eq!(chan_default.len(), 0);
180        chan_a.drain_messages();
181
182        // Test Case B
183        let msg_b = CanonicalMessage::from("payload_b").with_metadata_kv("route_key", "B");
184        switch.send(msg_b).await.unwrap();
185        assert_eq!(chan_a.len(), 0);
186        assert_eq!(chan_b.len(), 1);
187        assert_eq!(chan_default.len(), 0);
188        chan_b.drain_messages();
189
190        // Test Default (Unknown Key)
191        let msg_c =
192            CanonicalMessage::new(b"payload_c".to_vec(), None).with_metadata_kv("route_key", "C");
193        switch.send(msg_c).await.unwrap();
194        assert_eq!(chan_a.len(), 0);
195        assert_eq!(chan_b.len(), 0);
196        assert_eq!(chan_default.len(), 1);
197        chan_default.drain_messages();
198
199        // Test Default (Missing Key)
200        let msg_d = CanonicalMessage::new(b"payload_d".to_vec(), None);
201        switch.send(msg_d).await.unwrap();
202        assert_eq!(chan_a.len(), 0);
203        assert_eq!(chan_b.len(), 0);
204        assert_eq!(chan_default.len(), 1);
205    }
206}