1#![allow(dead_code)]
8
9use std::collections::HashMap;
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
14pub enum MessageType {
15 TaskSubmitted,
17 TaskAssigned,
19 TaskCompleted,
21 TaskFailed,
23 NodeJoined,
25 NodeLeft,
27 HealthChanged,
29 Heartbeat,
31 ConfigChanged,
33 Custom,
35}
36
37impl MessageType {
38 #[must_use]
40 pub fn topic(&self) -> &'static str {
41 match self {
42 Self::TaskSubmitted => "task.submitted",
43 Self::TaskAssigned => "task.assigned",
44 Self::TaskCompleted => "task.completed",
45 Self::TaskFailed => "task.failed",
46 Self::NodeJoined => "node.joined",
47 Self::NodeLeft => "node.left",
48 Self::HealthChanged => "node.health",
49 Self::Heartbeat => "node.heartbeat",
50 Self::ConfigChanged => "cluster.config",
51 Self::Custom => "custom",
52 }
53 }
54
55 #[must_use]
57 pub fn is_task_event(&self) -> bool {
58 matches!(
59 self,
60 Self::TaskSubmitted | Self::TaskAssigned | Self::TaskCompleted | Self::TaskFailed
61 )
62 }
63
64 #[must_use]
66 pub fn is_node_event(&self) -> bool {
67 matches!(
68 self,
69 Self::NodeJoined | Self::NodeLeft | Self::HealthChanged | Self::Heartbeat
70 )
71 }
72}
73
74#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
76pub struct BusMessage {
77 pub id: Uuid,
79 pub message_type: MessageType,
81 pub source: Uuid,
83 pub payload: String,
85 pub timestamp: i64,
87 pub correlation_id: Option<Uuid>,
89 pub headers: HashMap<String, String>,
91}
92
93impl BusMessage {
94 #[must_use]
96 pub fn new(message_type: MessageType, source: Uuid, payload: &str) -> Self {
97 Self {
98 id: Uuid::new_v4(),
99 message_type,
100 source,
101 payload: payload.to_string(),
102 timestamp: chrono::Utc::now().timestamp(),
103 correlation_id: None,
104 headers: HashMap::new(),
105 }
106 }
107
108 #[must_use]
110 pub fn with_correlation(mut self, correlation_id: Uuid) -> Self {
111 self.correlation_id = Some(correlation_id);
112 self
113 }
114
115 #[must_use]
117 pub fn with_header(mut self, key: &str, value: &str) -> Self {
118 self.headers.insert(key.to_string(), value.to_string());
119 self
120 }
121
122 #[must_use]
124 pub fn age_secs(&self, now: i64) -> i64 {
125 now - self.timestamp
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub struct SubscriptionId(Uuid);
132
133impl SubscriptionId {
134 #[must_use]
136 fn new() -> Self {
137 Self(Uuid::new_v4())
138 }
139}
140
141#[derive(Debug, Clone)]
143struct Subscription {
144 id: SubscriptionId,
146 subscriber_id: Uuid,
148 types: Vec<MessageType>,
150}
151
152#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
154pub struct BusStats {
155 pub messages_published: u64,
157 pub messages_delivered: u64,
159 pub messages_dropped: u64,
161 pub active_subscriptions: u64,
163}
164
165#[derive(Debug)]
171pub struct MessageBus {
172 subscriptions: Vec<Subscription>,
174 mailboxes: HashMap<SubscriptionId, Vec<BusMessage>>,
176 max_mailbox_size: usize,
178 stats: BusStats,
180}
181
182impl MessageBus {
183 #[must_use]
185 pub fn new() -> Self {
186 Self {
187 subscriptions: Vec::new(),
188 mailboxes: HashMap::new(),
189 max_mailbox_size: 1000,
190 stats: BusStats::default(),
191 }
192 }
193
194 #[must_use]
196 pub fn with_mailbox_size(max_size: usize) -> Self {
197 Self {
198 subscriptions: Vec::new(),
199 mailboxes: HashMap::new(),
200 max_mailbox_size: max_size,
201 stats: BusStats::default(),
202 }
203 }
204
205 pub fn subscribe(&mut self, subscriber_id: Uuid, types: Vec<MessageType>) -> SubscriptionId {
210 let sub_id = SubscriptionId::new();
211 self.subscriptions.push(Subscription {
212 id: sub_id,
213 subscriber_id,
214 types,
215 });
216 self.mailboxes.insert(sub_id, Vec::new());
217 self.stats.active_subscriptions += 1;
218 sub_id
219 }
220
221 pub fn unsubscribe(&mut self, sub_id: &SubscriptionId) -> bool {
223 let before = self.subscriptions.len();
224 self.subscriptions.retain(|s| s.id != *sub_id);
225 self.mailboxes.remove(sub_id);
226 let removed = self.subscriptions.len() < before;
227 if removed {
228 self.stats.active_subscriptions = self.stats.active_subscriptions.saturating_sub(1);
229 }
230 removed
231 }
232
233 pub fn publish(&mut self, message: BusMessage) -> usize {
237 self.stats.messages_published += 1;
238
239 let matching_subs: Vec<SubscriptionId> = self
240 .subscriptions
241 .iter()
242 .filter(|sub| sub.types.contains(&message.message_type))
243 .map(|sub| sub.id)
244 .collect();
245
246 if matching_subs.is_empty() {
247 self.stats.messages_dropped += 1;
248 return 0;
249 }
250
251 let mut delivered = 0;
252 for sub_id in &matching_subs {
253 if let Some(mailbox) = self.mailboxes.get_mut(sub_id) {
254 if mailbox.len() < self.max_mailbox_size {
255 mailbox.push(message.clone());
256 delivered += 1;
257 }
258 }
259 }
260
261 self.stats.messages_delivered += delivered as u64;
262 delivered
263 }
264
265 pub fn receive(&mut self, sub_id: &SubscriptionId) -> Vec<BusMessage> {
269 self.mailboxes
270 .get_mut(sub_id)
271 .map(std::mem::take)
272 .unwrap_or_default()
273 }
274
275 #[must_use]
277 pub fn pending_count(&self, sub_id: &SubscriptionId) -> usize {
278 self.mailboxes.get(sub_id).map_or(0, Vec::len)
279 }
280
281 #[must_use]
283 pub fn subscription_count(&self) -> usize {
284 self.subscriptions.len()
285 }
286
287 #[must_use]
289 pub fn stats(&self) -> &BusStats {
290 &self.stats
291 }
292
293 pub fn clear_mailboxes(&mut self) {
295 for mailbox in self.mailboxes.values_mut() {
296 mailbox.clear();
297 }
298 }
299}
300
301impl Default for MessageBus {
302 fn default() -> Self {
303 Self::new()
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 fn src() -> Uuid {
312 Uuid::new_v4()
313 }
314
315 #[test]
316 fn test_message_type_topics() {
317 assert_eq!(MessageType::TaskSubmitted.topic(), "task.submitted");
318 assert_eq!(MessageType::NodeJoined.topic(), "node.joined");
319 assert_eq!(MessageType::Custom.topic(), "custom");
320 }
321
322 #[test]
323 fn test_message_type_is_task_event() {
324 assert!(MessageType::TaskSubmitted.is_task_event());
325 assert!(MessageType::TaskCompleted.is_task_event());
326 assert!(!MessageType::NodeJoined.is_task_event());
327 assert!(!MessageType::Heartbeat.is_task_event());
328 }
329
330 #[test]
331 fn test_message_type_is_node_event() {
332 assert!(MessageType::NodeJoined.is_node_event());
333 assert!(MessageType::Heartbeat.is_node_event());
334 assert!(!MessageType::TaskSubmitted.is_node_event());
335 }
336
337 #[test]
338 fn test_bus_message_creation() {
339 let s = src();
340 let msg = BusMessage::new(MessageType::TaskSubmitted, s, "{\"task_id\":\"abc\"}");
341 assert_eq!(msg.message_type, MessageType::TaskSubmitted);
342 assert_eq!(msg.source, s);
343 assert!(msg.correlation_id.is_none());
344 }
345
346 #[test]
347 fn test_bus_message_with_correlation() {
348 let corr = Uuid::new_v4();
349 let msg = BusMessage::new(MessageType::TaskCompleted, src(), "{}").with_correlation(corr);
350 assert_eq!(msg.correlation_id, Some(corr));
351 }
352
353 #[test]
354 fn test_bus_message_with_header() {
355 let msg = BusMessage::new(MessageType::Custom, src(), "{}")
356 .with_header("priority", "high")
357 .with_header("region", "us-east");
358 assert_eq!(msg.headers.len(), 2);
359 assert_eq!(
360 msg.headers
361 .get("priority")
362 .expect("get should return a value"),
363 "high"
364 );
365 }
366
367 #[test]
368 fn test_bus_message_age() {
369 let mut msg = BusMessage::new(MessageType::Heartbeat, src(), "{}");
370 msg.timestamp = 1000;
371 assert_eq!(msg.age_secs(1050), 50);
372 }
373
374 #[test]
375 fn test_subscribe_and_publish() {
376 let mut bus = MessageBus::new();
377 let sub = bus.subscribe(src(), vec![MessageType::TaskSubmitted]);
378 let msg = BusMessage::new(MessageType::TaskSubmitted, src(), "{}");
379 let delivered = bus.publish(msg);
380 assert_eq!(delivered, 1);
381 let received = bus.receive(&sub);
382 assert_eq!(received.len(), 1);
383 }
384
385 #[test]
386 fn test_publish_no_subscribers() {
387 let mut bus = MessageBus::new();
388 let msg = BusMessage::new(MessageType::TaskSubmitted, src(), "{}");
389 let delivered = bus.publish(msg);
390 assert_eq!(delivered, 0);
391 assert_eq!(bus.stats().messages_dropped, 1);
392 }
393
394 #[test]
395 fn test_subscribe_filters_by_type() {
396 let mut bus = MessageBus::new();
397 let sub = bus.subscribe(src(), vec![MessageType::TaskCompleted]);
398 bus.publish(BusMessage::new(MessageType::TaskSubmitted, src(), "{}"));
400 let received = bus.receive(&sub);
401 assert!(received.is_empty());
402 }
403
404 #[test]
405 fn test_multiple_subscribers() {
406 let mut bus = MessageBus::new();
407 let sub1 = bus.subscribe(src(), vec![MessageType::NodeJoined]);
408 let sub2 = bus.subscribe(src(), vec![MessageType::NodeJoined]);
409 bus.publish(BusMessage::new(MessageType::NodeJoined, src(), "{}"));
410 assert_eq!(bus.receive(&sub1).len(), 1);
411 assert_eq!(bus.receive(&sub2).len(), 1);
412 }
413
414 #[test]
415 fn test_unsubscribe() {
416 let mut bus = MessageBus::new();
417 let sub = bus.subscribe(src(), vec![MessageType::Heartbeat]);
418 assert_eq!(bus.subscription_count(), 1);
419 assert!(bus.unsubscribe(&sub));
420 assert_eq!(bus.subscription_count(), 0);
421 }
422
423 #[test]
424 fn test_pending_count() {
425 let mut bus = MessageBus::new();
426 let sub = bus.subscribe(src(), vec![MessageType::TaskFailed]);
427 assert_eq!(bus.pending_count(&sub), 0);
428 bus.publish(BusMessage::new(MessageType::TaskFailed, src(), "{}"));
429 bus.publish(BusMessage::new(MessageType::TaskFailed, src(), "{}"));
430 assert_eq!(bus.pending_count(&sub), 2);
431 }
432
433 #[test]
434 fn test_mailbox_size_limit() {
435 let mut bus = MessageBus::with_mailbox_size(2);
436 let sub = bus.subscribe(src(), vec![MessageType::Heartbeat]);
437 bus.publish(BusMessage::new(MessageType::Heartbeat, src(), "1"));
438 bus.publish(BusMessage::new(MessageType::Heartbeat, src(), "2"));
439 bus.publish(BusMessage::new(MessageType::Heartbeat, src(), "3"));
440 assert_eq!(bus.pending_count(&sub), 2); }
442
443 #[test]
444 fn test_clear_mailboxes() {
445 let mut bus = MessageBus::new();
446 let sub = bus.subscribe(src(), vec![MessageType::TaskAssigned]);
447 bus.publish(BusMessage::new(MessageType::TaskAssigned, src(), "{}"));
448 bus.clear_mailboxes();
449 assert_eq!(bus.pending_count(&sub), 0);
450 assert_eq!(bus.subscription_count(), 1); }
452
453 #[test]
454 fn test_bus_stats() {
455 let mut bus = MessageBus::new();
456 let _sub = bus.subscribe(src(), vec![MessageType::TaskSubmitted]);
457 bus.publish(BusMessage::new(MessageType::TaskSubmitted, src(), "{}"));
458 bus.publish(BusMessage::new(MessageType::NodeLeft, src(), "{}")); assert_eq!(bus.stats().messages_published, 2);
460 assert_eq!(bus.stats().messages_delivered, 1);
461 assert_eq!(bus.stats().messages_dropped, 1);
462 }
463}