1use std::{
22 cmp::Ordering,
23 fmt::Debug,
24 hash::{Hash, Hasher},
25};
26
27use indexmap::{Equivalent, IndexMap, IndexSet};
28use smallvec::SmallVec;
29use ustr::Ustr;
30
31use super::{
32 matching::is_matching_backtracking,
33 mstr::{MStr, Pattern, Topic},
34 typed_handler::TypedHandler,
35};
36
37#[derive(Clone)]
41pub struct TypedSubscription<T: 'static> {
42 pub handler: TypedHandler<T>,
44 pub handler_id: Ustr,
46 pub pattern: MStr<Pattern>,
48 pub priority: u32,
50}
51
52impl<T: 'static> TypedSubscription<T> {
53 #[must_use]
55 pub fn new(pattern: MStr<Pattern>, handler: TypedHandler<T>, priority: Option<u32>) -> Self {
56 Self {
57 handler_id: handler.id(),
58 pattern,
59 handler,
60 priority: priority.unwrap_or(0),
61 }
62 }
63
64 fn delivery_order(&self, other: &Self) -> Ordering {
65 other
66 .priority
67 .cmp(&self.priority)
68 .then_with(|| self.pattern.cmp(&other.pattern))
69 .then_with(|| self.handler_id.cmp(&other.handler_id))
70 }
71}
72
73impl<T: 'static> Debug for TypedSubscription<T> {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct(stringify!(TypedSubscription))
76 .field("handler_id", &self.handler_id)
77 .field("pattern", &self.pattern)
78 .field("priority", &self.priority)
79 .field("type", &std::any::type_name::<T>())
80 .finish()
81 }
82}
83
84impl<T: 'static> PartialEq for TypedSubscription<T> {
85 fn eq(&self, other: &Self) -> bool {
86 self.pattern == other.pattern && self.handler_id == other.handler_id
87 }
88}
89
90impl<T: 'static> Eq for TypedSubscription<T> {}
91
92impl<T: 'static> PartialOrd for TypedSubscription<T> {
93 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94 Some(self.cmp(other))
95 }
96}
97
98impl<T: 'static> Ord for TypedSubscription<T> {
99 fn cmp(&self, other: &Self) -> Ordering {
100 self.pattern
101 .cmp(&other.pattern)
102 .then_with(|| self.handler_id.cmp(&other.handler_id))
103 }
104}
105
106impl<T: 'static> Hash for TypedSubscription<T> {
107 fn hash<H: Hasher>(&self, state: &mut H) {
108 self.pattern.hash(state);
109 self.handler_id.hash(state);
110 }
111}
112
113impl<T: 'static> Equivalent<TypedSubscription<T>> for (MStr<Pattern>, Ustr) {
114 fn equivalent(&self, other: &TypedSubscription<T>) -> bool {
115 self.0 == other.pattern && self.1 == other.handler_id
116 }
117}
118
119#[derive(Debug)]
124pub struct TopicRouter<T: 'static> {
125 pub(crate) subscriptions: IndexSet<TypedSubscription<T>>,
127 topic_cache: IndexMap<MStr<Topic>, SmallVec<[usize; 64]>>,
129}
130
131impl<T: 'static> Default for TopicRouter<T> {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137impl<T: 'static> TopicRouter<T> {
138 #[must_use]
140 pub fn new() -> Self {
141 Self {
142 subscriptions: IndexSet::new(),
143 topic_cache: IndexMap::new(),
144 }
145 }
146
147 #[must_use]
149 pub fn subscription_count(&self) -> usize {
150 self.subscriptions.len()
151 }
152
153 #[must_use]
155 pub fn is_empty(&self) -> bool {
156 self.subscriptions.is_empty()
157 }
158
159 #[must_use]
161 pub fn patterns(&self) -> Vec<&str> {
162 self.ordered_subscriptions()
163 .into_iter()
164 .map(|s| s.pattern.as_str())
165 .collect()
166 }
167
168 #[must_use]
170 pub fn handler_ids(&self) -> Vec<&str> {
171 self.ordered_subscriptions()
172 .into_iter()
173 .map(|s| s.handler_id.as_str())
174 .collect()
175 }
176
177 pub fn subscribe(&mut self, pattern: MStr<Pattern>, handler: TypedHandler<T>, priority: u32) {
184 let sub = TypedSubscription::new(pattern, handler, Some(priority));
185
186 if self.subscriptions.contains(&sub) {
189 log::debug!("{sub:?} already exists; skipping duplicate subscription");
190 return;
191 }
192
193 log::debug!("Subscribing {sub:?}");
194
195 self.subscriptions.insert(sub);
196 self.topic_cache.clear();
197 }
198
199 pub fn unsubscribe(&mut self, pattern: MStr<Pattern>, handler: &TypedHandler<T>) {
201 log::debug!(
202 "Unsubscribing handler {} from pattern '{pattern}'",
203 handler.id()
204 );
205
206 let handler_id = handler.id();
207
208 if self.subscriptions.swap_remove(&(pattern, handler_id)) {
209 self.topic_cache.clear();
211
212 log::debug!("Handler for pattern '{pattern}' was removed");
213 } else {
214 log::debug!("No matching handler for pattern '{pattern}' was found");
215 }
216 }
217
218 pub fn remove_handler(&mut self, pattern: MStr<Pattern>, handler_id: Ustr) {
220 if self.subscriptions.swap_remove(&(pattern, handler_id)) {
221 self.topic_cache.clear();
223 log::debug!("Handler {handler_id} for pattern '{pattern}' was removed");
224 }
225 }
226
227 #[must_use]
229 pub fn is_subscribed(&self, pattern: MStr<Pattern>, handler: &TypedHandler<T>) -> bool {
230 let handler_id = handler.id();
231 self.subscriptions.contains(&(pattern, handler_id))
232 }
233
234 #[must_use]
236 pub fn has_subscribers(&self, topic: MStr<Topic>) -> bool {
237 self.get_matching_indices(topic).map_or_else(
238 || !Self::find_matches(&self.subscriptions, topic).is_empty(),
239 |indices| !indices.is_empty(),
240 )
241 }
242
243 #[must_use]
245 pub fn subscriber_count(&self, topic: MStr<Topic>) -> usize {
246 self.get_matching_indices(topic).map_or_else(
247 || Self::find_matches(&self.subscriptions, topic).len(),
248 <[usize]>::len,
249 )
250 }
251
252 #[must_use]
255 pub fn exact_subscriber_count(&self, topic: MStr<Topic>) -> usize {
256 let pattern: MStr<Pattern> = topic.into();
257 self.subscriptions
258 .iter()
259 .filter(|s| s.pattern == pattern)
260 .count()
261 }
262
263 pub fn publish(&mut self, topic: MStr<Topic>, message: &T) {
265 let Self {
267 subscriptions,
268 topic_cache,
269 } = self;
270
271 let indices = topic_cache
272 .entry(topic)
273 .or_insert_with(|| Self::find_matches(subscriptions, topic));
274 let subscriptions = subscriptions.as_slice();
275
276 for &idx in indices.iter() {
277 subscriptions[idx].handler.handle(message);
278 }
279 }
280
281 pub fn get_matching_handlers(&mut self, topic: MStr<Topic>) -> Vec<TypedHandler<T>> {
287 let indices: SmallVec<[usize; 64]> = self
288 .get_or_compute_matching_indices(topic)
289 .iter()
290 .copied()
291 .collect();
292 indices
293 .into_iter()
294 .map(|idx| self.subscriptions[idx].handler.clone())
295 .collect()
296 }
297
298 fn get_matching_indices(&self, topic: MStr<Topic>) -> Option<&[usize]> {
300 self.topic_cache.get(&topic).map(SmallVec::as_slice)
301 }
302
303 fn get_or_compute_matching_indices(&mut self, topic: MStr<Topic>) -> &[usize] {
305 let Self {
306 subscriptions,
307 topic_cache,
308 } = self;
309
310 topic_cache
311 .entry(topic)
312 .or_insert_with(|| Self::find_matches(subscriptions, topic))
313 .as_slice()
314 }
315
316 pub(crate) fn fill_matching_handlers(
318 &mut self,
319 topic: MStr<Topic>,
320 buf: &mut SmallVec<[TypedHandler<T>; 64]>,
321 ) {
322 let Self {
323 subscriptions,
324 topic_cache,
325 } = self;
326
327 let indices = topic_cache
328 .entry(topic)
329 .or_insert_with(|| Self::find_matches(subscriptions, topic));
330 let subscriptions = subscriptions.as_slice();
331
332 for &idx in indices.iter() {
333 buf.push(subscriptions[idx].handler.clone());
334 }
335 }
336
337 fn find_matches(
339 subscriptions: &IndexSet<TypedSubscription<T>>,
340 topic: MStr<Topic>,
341 ) -> SmallVec<[usize; 64]> {
342 let mut indices: SmallVec<[usize; 64]> = subscriptions
343 .iter()
344 .enumerate()
345 .filter_map(|(idx, sub)| is_matching_backtracking(topic, sub.pattern).then_some(idx))
346 .collect();
347
348 indices.sort_by(|&a, &b| subscriptions[a].delivery_order(&subscriptions[b]));
350 indices
351 }
352
353 fn ordered_subscriptions(&self) -> Vec<&TypedSubscription<T>> {
354 let mut subscriptions: Vec<_> = self.subscriptions.iter().collect();
355 subscriptions.sort_by(|a, b| a.delivery_order(b));
356 subscriptions
357 }
358
359 pub fn clear(&mut self) {
361 self.subscriptions.clear();
362 self.topic_cache.clear();
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use std::{
369 cell::RefCell,
370 collections::hash_map::DefaultHasher,
371 hash::{Hash, Hasher},
372 rc::Rc,
373 };
374
375 use rstest::rstest;
376
377 use super::*;
378
379 #[rstest]
380 fn test_typed_subscription_ordering_laws() {
381 let handler = TypedHandler::from_with_id("handler-b", |_: &i32| {});
382 let base = TypedSubscription::new("pattern-b".into(), handler.clone(), Some(1));
383 let different_priority = TypedSubscription::new("pattern-b".into(), handler, Some(2));
384 let different_pattern = TypedSubscription::new(
385 "pattern-a".into(),
386 TypedHandler::from_with_id("handler-b", |_: &i32| {}),
387 Some(1),
388 );
389 let different_handler = TypedSubscription::new(
390 "pattern-b".into(),
391 TypedHandler::from_with_id("handler-a", |_: &i32| {}),
392 Some(1),
393 );
394
395 assert_eq!(base, different_priority);
396 assert_eq!(base.cmp(&different_priority), Ordering::Equal);
397
398 let mut base_hasher = DefaultHasher::new();
399 base.hash(&mut base_hasher);
400 let mut different_priority_hasher = DefaultHasher::new();
401 different_priority.hash(&mut different_priority_hasher);
402 assert_eq!(base_hasher.finish(), different_priority_hasher.finish());
403
404 let variants = [
405 base,
406 different_priority,
407 different_pattern,
408 different_handler,
409 ];
410
411 for a in &variants {
412 for b in &variants {
413 assert_eq!(a == b, a.cmp(b).is_eq());
414 assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
415 assert_eq!(a.cmp(b), b.cmp(a).reverse());
416 }
417 }
418 }
419
420 #[rstest]
421 fn test_topic_router_subscribe_and_publish() {
422 let mut router = TopicRouter::<String>::new();
423 let received = Rc::new(RefCell::new(Vec::new()));
424 let received_clone = received.clone();
425
426 let handler = TypedHandler::from(move |msg: &String| {
427 received_clone.borrow_mut().push(msg.clone());
428 });
429
430 router.subscribe("data.quotes.*".into(), handler, 0);
431
432 let topic: MStr<Topic> = "data.quotes.AAPL".into();
433 router.publish(topic, &"quote1".to_string());
434 router.publish(topic, &"quote2".to_string());
435
436 assert_eq!(*received.borrow(), vec!["quote1", "quote2"]);
437 }
438
439 #[rstest]
440 fn test_topic_router_publish_orders_by_full_delivery_key() {
441 let mut router = TopicRouter::<i32>::new();
442 let order = Rc::new(RefCell::new(Vec::new()));
443
444 let low_order = order.clone();
445 let low = TypedHandler::from_with_id("handler-z", move |_: &i32| {
446 low_order.borrow_mut().push("low-z");
447 });
448 let exact_b_order = order.clone();
449 let exact_b = TypedHandler::from_with_id("handler-b", move |_: &i32| {
450 exact_b_order.borrow_mut().push("exact-b");
451 });
452 let exact_a_order = order.clone();
453 let exact_a = TypedHandler::from_with_id("handler-a", move |_: &i32| {
454 exact_a_order.borrow_mut().push("exact-a");
455 });
456 let wildcard_b_order = order.clone();
457 let wildcard_b = TypedHandler::from_with_id("handler-b", move |_: &i32| {
458 wildcard_b_order.borrow_mut().push("wildcard-b");
459 });
460
461 router.subscribe("delivery.*".into(), low, 1);
462 router.subscribe("delivery.topic".into(), exact_b, 10);
463 router.subscribe("delivery.topic".into(), exact_a, 10);
464 router.subscribe("delivery.*".into(), wildcard_b, 10);
465 router.subscribe(
466 "delivery.topic".into(),
467 TypedHandler::from_with_id("handler-b", |_: &i32| panic!("duplicate handler replaced")),
468 100,
469 );
470
471 let topic: MStr<Topic> = "delivery.topic".into();
472 router.publish(topic, &42);
473
474 assert_eq!(
475 *order.borrow(),
476 vec!["wildcard-b", "exact-a", "exact-b", "low-z"]
477 );
478
479 router.remove_handler("delivery.*".into(), "handler-z".into());
480 order.borrow_mut().clear();
481 router.publish(topic, &42);
482 assert_eq!(*order.borrow(), vec!["wildcard-b", "exact-a", "exact-b"]);
483 }
484
485 #[rstest]
486 fn test_topic_router_unsubscribe() {
487 let mut router = TopicRouter::<String>::new();
488 let received = Rc::new(RefCell::new(Vec::new()));
489 let received_clone = received.clone();
490
491 let handler = TypedHandler::from_with_id("test-handler", move |msg: &String| {
492 received_clone.borrow_mut().push(msg.clone());
493 });
494
495 router.subscribe("data.*".into(), handler.clone(), 0);
496 assert!(router.is_subscribed("data.*".into(), &handler));
497
498 router.unsubscribe("data.*".into(), &handler);
499 assert!(!router.is_subscribed("data.*".into(), &handler));
500
501 let topic: MStr<Topic> = "data.test".into();
502 router.publish(topic, &"test".to_string());
503
504 assert!(received.borrow().is_empty());
506 }
507
508 #[rstest]
509 fn test_topic_router_duplicate_subscription() {
510 let mut router = TopicRouter::<i32>::new();
511
512 let handler1 = TypedHandler::from_with_id("dup-handler", |_: &i32| {});
513 let handler2 = TypedHandler::from_with_id("dup-handler", |_: &i32| {});
514
515 router.subscribe("test.*".into(), handler1, 0);
516 router.subscribe("test.*".into(), handler2, 0);
517
518 assert_eq!(router.subscription_count(), 1);
520 }
521
522 #[rstest]
523 fn test_topic_router_wildcard_patterns() {
524 let mut router = TopicRouter::<String>::new();
525 let received = Rc::new(RefCell::new(Vec::new()));
526 let received_clone = received.clone();
527
528 let handler = TypedHandler::from(move |msg: &String| {
529 received_clone.borrow_mut().push(msg.clone());
530 });
531
532 router.subscribe("data.*.AAPL".into(), handler, 0);
533
534 let topic1: MStr<Topic> = "data.quotes.AAPL".into();
536 router.publish(topic1, &"match1".to_string());
537
538 let topic2: MStr<Topic> = "data.trades.AAPL".into();
539 router.publish(topic2, &"match2".to_string());
540
541 let topic3: MStr<Topic> = "data.quotes.MSFT".into();
543 router.publish(topic3, &"no-match".to_string());
544
545 assert_eq!(*received.borrow(), vec!["match1", "match2"]);
546 }
547
548 #[rstest]
549 fn test_topic_router_cache_populated_on_publish() {
550 let mut router = TopicRouter::<i32>::new();
551 let handler = TypedHandler::from_with_id("cache-test", |_: &i32| {});
552
553 router.subscribe("data.*".into(), handler, 0);
554
555 let topic: MStr<Topic> = "data.quotes".into();
557 router.publish(topic, &1);
558
559 assert_eq!(router.subscriber_count(topic), 1);
561 }
562
563 #[rstest]
564 fn test_topic_router_cache_invalidated_on_subscribe() {
565 let mut router = TopicRouter::<i32>::new();
566 let received = Rc::new(RefCell::new(0));
567
568 let r1 = received.clone();
569 let handler1 = TypedHandler::from_with_id("h1", move |_: &i32| {
570 *r1.borrow_mut() += 1;
571 });
572
573 router.subscribe("data.*".into(), handler1, 0);
574
575 let topic: MStr<Topic> = "data.test".into();
577 router.publish(topic, &1);
578 assert_eq!(*received.borrow(), 1);
579
580 let r2 = received.clone();
582 let handler2 = TypedHandler::from_with_id("h2", move |_: &i32| {
583 *r2.borrow_mut() += 10;
584 });
585 router.subscribe("data.*".into(), handler2, 0);
586
587 router.publish(topic, &2);
589 assert_eq!(*received.borrow(), 12); }
591
592 #[rstest]
593 fn test_topic_router_late_distinct_wildcard_receives_cached_topic() {
594 let mut router = TopicRouter::<String>::new();
595 let topic: MStr<Topic> = "data.instrument.POLYMARKET.TEST-SYMBOL".into();
596
597 let early = Rc::new(RefCell::new(Vec::new()));
598 let early_clone = early.clone();
599 let early_handler = TypedHandler::from_with_id("early", move |msg: &String| {
600 early_clone.borrow_mut().push(msg.clone());
601 });
602 router.subscribe("data.*.POLYMARKET.*".into(), early_handler, 0);
603
604 router.publish(topic, &"ONE".to_string());
605
606 let late = Rc::new(RefCell::new(Vec::new()));
607 let late_clone = late.clone();
608 let late_handler = TypedHandler::from_with_id("late", move |msg: &String| {
609 late_clone.borrow_mut().push(msg.clone());
610 });
611 router.subscribe("data.instrument.POLYMARKET.*".into(), late_handler, 0);
612
613 router.publish(topic, &"TWO".to_string());
614
615 assert_eq!(*early.borrow(), vec!["ONE", "TWO"]);
616 assert_eq!(*late.borrow(), vec!["TWO"]);
617 }
618
619 #[rstest]
620 fn test_topic_router_cache_invalidated_on_unsubscribe() {
621 let mut router = TopicRouter::<i32>::new();
622 let received = Rc::new(RefCell::new(0));
623
624 let r1 = received.clone();
625 let handler1 = TypedHandler::from_with_id("h1", move |_: &i32| {
626 *r1.borrow_mut() += 1;
627 });
628
629 let r2 = received.clone();
630 let handler2 = TypedHandler::from_with_id("h2", move |_: &i32| {
631 *r2.borrow_mut() += 10;
632 });
633
634 router.subscribe("data.*".into(), handler1.clone(), 0);
635 router.subscribe("data.*".into(), handler2, 0);
636
637 let topic: MStr<Topic> = "data.test".into();
639 router.publish(topic, &1);
640 assert_eq!(*received.borrow(), 11); router.unsubscribe("data.*".into(), &handler1);
644
645 router.publish(topic, &2);
647 assert_eq!(*received.borrow(), 21); }
649
650 #[rstest]
651 fn test_topic_router_has_subscribers() {
652 let mut router = TopicRouter::<i32>::new();
653
654 let topic: MStr<Topic> = "data.quotes.AAPL".into();
655 assert!(!router.has_subscribers(topic));
656
657 let handler = TypedHandler::from_with_id("test", |_: &i32| {});
658 router.subscribe("data.quotes.*".into(), handler, 0);
659
660 assert!(router.has_subscribers(topic));
661 }
662
663 #[rstest]
664 fn test_topic_router_subscriber_count() {
665 let mut router = TopicRouter::<i32>::new();
666
667 let topic: MStr<Topic> = "data.quotes.AAPL".into();
668 assert_eq!(router.subscriber_count(topic), 0);
669
670 let handler1 = TypedHandler::from_with_id("h1", |_: &i32| {});
671 let handler2 = TypedHandler::from_with_id("h2", |_: &i32| {});
672 let handler3 = TypedHandler::from_with_id("h3", |_: &i32| {});
673
674 router.subscribe("data.quotes.*".into(), handler1, 0);
675 router.subscribe("data.*.AAPL".into(), handler2, 0);
676 router.subscribe("events.*".into(), handler3, 0); assert_eq!(router.subscriber_count(topic), 2);
679 }
680
681 #[rstest]
682 fn test_topic_router_patterns_and_handler_ids() {
683 let mut router = TopicRouter::<i32>::new();
684
685 let handler1 = TypedHandler::from_with_id("handler-a", |_: &i32| {});
686 let handler2 = TypedHandler::from_with_id("handler-b", |_: &i32| {});
687
688 router.subscribe("pattern.one".into(), handler1, 0);
689 router.subscribe("pattern.two".into(), handler2, 1);
690
691 assert_eq!(router.patterns(), vec!["pattern.two", "pattern.one"]);
692 assert_eq!(router.handler_ids(), vec!["handler-b", "handler-a"]);
693 }
694
695 #[rstest]
696 fn test_topic_router_clear() {
697 let mut router = TopicRouter::<i32>::new();
698 let handler = TypedHandler::from_with_id("clear-test", |_: &i32| {});
699
700 router.subscribe("data.*".into(), handler, 0);
701
702 let topic: MStr<Topic> = "data.test".into();
704 router.publish(topic, &1);
705
706 assert_eq!(router.subscription_count(), 1);
707 assert!(!router.is_empty());
708
709 router.clear();
710
711 assert_eq!(router.subscription_count(), 0);
712 assert!(router.is_empty());
713 assert!(!router.has_subscribers(topic));
714 }
715
716 #[rstest]
717 fn test_topic_router_multiple_patterns_same_topic() {
718 let mut router = TopicRouter::<i32>::new();
719 let received = Rc::new(RefCell::new(Vec::new()));
720
721 let r1 = received.clone();
722 let handler1 = TypedHandler::from_with_id("specific", move |v: &i32| {
723 r1.borrow_mut().push(format!("specific:{v}"));
724 });
725
726 let r2 = received.clone();
727 let handler2 = TypedHandler::from_with_id("wildcard", move |v: &i32| {
728 r2.borrow_mut().push(format!("wildcard:{v}"));
729 });
730
731 let r3 = received.clone();
732 let handler3 = TypedHandler::from_with_id("all", move |v: &i32| {
733 r3.borrow_mut().push(format!("all:{v}"));
734 });
735
736 router.subscribe("data.quotes.AAPL".into(), handler1, 0);
738 router.subscribe("data.quotes.*".into(), handler2, 0);
739 router.subscribe("data.*.*".into(), handler3, 0);
740
741 let topic: MStr<Topic> = "data.quotes.AAPL".into();
742 router.publish(topic, &42);
743
744 let msgs = received.borrow();
745 assert_eq!(msgs.len(), 3);
746 assert!(msgs.contains(&"specific:42".to_string()));
747 assert!(msgs.contains(&"wildcard:42".to_string()));
748 assert!(msgs.contains(&"all:42".to_string()));
749 }
750
751 #[rstest]
752 fn test_remove_handler_invalidates_cross_pattern_cache() {
753 let mut router = TopicRouter::<i32>::new();
754 let count_a = Rc::new(RefCell::new(0));
755 let count_b = Rc::new(RefCell::new(0));
756
757 let ca = count_a.clone();
758 let handler_a = TypedHandler::from_with_id("ha", move |_: &i32| {
759 *ca.borrow_mut() += 1;
760 });
761 let handler_a_id = Ustr::from("ha");
762
763 let cb = count_b.clone();
764 let handler_b = TypedHandler::from_with_id("hb", move |_: &i32| {
765 *cb.borrow_mut() += 1;
766 });
767
768 router.subscribe("events.order.S-001".into(), handler_a, 0);
769 router.subscribe("events.order.S-002".into(), handler_b, 0);
770
771 let topic_a: MStr<Topic> = "events.order.S-001".into();
772 let topic_b: MStr<Topic> = "events.order.S-002".into();
773 router.publish(topic_a, &1);
774 router.publish(topic_b, &1);
775 assert_eq!(*count_a.borrow(), 1);
776 assert_eq!(*count_b.borrow(), 1);
777
778 router.remove_handler("events.order.S-001".into(), handler_a_id);
780
781 router.publish(topic_b, &2);
783 assert_eq!(*count_b.borrow(), 2);
784
785 router.publish(topic_a, &3);
786 assert_eq!(*count_a.borrow(), 1);
787 }
788
789 #[rstest]
790 fn test_remove_handler_only_removes_targeted_handler() {
791 let mut router = TopicRouter::<i32>::new();
792 let count_own = Rc::new(RefCell::new(0));
793 let count_other = Rc::new(RefCell::new(0));
794
795 let co = count_own.clone();
796 let handler_own = TypedHandler::from_with_id("strategy", move |_: &i32| {
797 *co.borrow_mut() += 1;
798 });
799 let own_id = Ustr::from("strategy");
800
801 let cother = count_other.clone();
802 let handler_other = TypedHandler::from_with_id("exec-algo", move |_: &i32| {
803 *cother.borrow_mut() += 1;
804 });
805
806 let pattern: MStr<Pattern> = "events.order.S-001".into();
808 router.subscribe(pattern, handler_own, 0);
809 router.subscribe(pattern, handler_other, 0);
810
811 let topic: MStr<Topic> = "events.order.S-001".into();
812 router.publish(topic, &1);
813 assert_eq!(*count_own.borrow(), 1);
814 assert_eq!(*count_other.borrow(), 1);
815
816 router.remove_handler(pattern, own_id);
817
818 router.publish(topic, &2);
819 assert_eq!(*count_own.borrow(), 1);
820 assert_eq!(*count_other.borrow(), 2);
821 }
822
823 #[rstest]
824 fn test_unsubscribe_one_pattern_does_not_break_other_patterns() {
825 let mut router = TopicRouter::<i32>::new();
826 let received = Rc::new(RefCell::new(0));
827
828 let alpha = TypedHandler::from_with_id("alpha", |_: &i32| {});
829
830 let received_beta = received.clone();
831 let beta = TypedHandler::from_with_id("beta", move |_: &i32| {
832 *received_beta.borrow_mut() += 1;
833 });
834
835 router.subscribe("alpha.*".into(), alpha.clone(), 0);
836 router.subscribe("beta.*".into(), beta, 0);
837
838 let beta_topic: MStr<Topic> = "beta.topic".into();
839 router.publish(beta_topic, &1);
840 assert_eq!(*received.borrow(), 1);
841
842 router.unsubscribe("alpha.*".into(), &alpha);
843
844 router.publish(beta_topic, &2);
845 assert_eq!(*received.borrow(), 2);
846 }
847}