1use std::{
87 any::{Any, TypeId},
88 cell::RefCell,
89 collections::HashMap,
90 fmt::Debug,
91 hash::{Hash, Hasher},
92 rc::Rc,
93};
94
95use ahash::{AHashMap, AHashSet};
96use indexmap::IndexMap;
97use nautilus_core::UUID4;
98use nautilus_model::{
99 data::{
100 Bar, Data, FundingRateUpdate, GreeksData, IndexPriceUpdate, MarkPriceUpdate,
101 OrderBookDeltas, OrderBookDepth, QuoteTick, TradeTick,
102 option_chain::{OptionChainSlice, OptionGreeks},
103 },
104 events::{AccountState, OrderEventAny, PortfolioSnapshot, PositionEvent},
105 identifiers::TraderId,
106 instruments::InstrumentAny,
107 orderbook::OrderBook,
108 orders::OrderAny,
109 position::Position,
110};
111use smallvec::SmallVec;
112use ustr::Ustr;
113
114use super::{
115 HAS_EXTERNAL_EGRESS, ShareableMessageHandler,
116 backing::MessageBusExternalEgress,
117 config::MessageBusConfig,
118 matching::is_matching_backtracking,
119 message::{BusPayloadCategory, BusPayloadType},
120 mstr::{Endpoint, MStr, Pattern, Topic},
121 set_message_bus,
122 switchboard::MessagingSwitchboard,
123 typed_endpoints::{EndpointMap, IntoEndpointMap},
124 typed_router::TopicRouter,
125};
126use crate::{
127 enums::SerializationEncoding,
128 messages::{
129 data::{DataCommand, DataResponse},
130 execution::{ExecutionReport, TradingCommand},
131 },
132};
133
134#[derive(Clone, Debug)]
139pub struct Subscription {
140 pub handler: ShareableMessageHandler,
142 pub handler_id: Ustr,
144 pub pattern: MStr<Pattern>,
146 pub priority: u32,
150}
151
152impl Subscription {
153 #[must_use]
155 pub fn new(
156 pattern: MStr<Pattern>,
157 handler: ShareableMessageHandler,
158 priority: Option<u32>,
159 ) -> Self {
160 Self {
161 handler_id: handler.0.id(),
162 pattern,
163 handler,
164 priority: priority.unwrap_or(0),
165 }
166 }
167
168 pub(crate) fn delivery_order(&self, other: &Self) -> std::cmp::Ordering {
169 other
170 .priority
171 .cmp(&self.priority)
172 .then_with(|| self.pattern.cmp(&other.pattern))
173 .then_with(|| self.handler_id.cmp(&other.handler_id))
174 }
175}
176
177impl PartialEq<Self> for Subscription {
178 fn eq(&self, other: &Self) -> bool {
179 self.pattern == other.pattern && self.handler_id == other.handler_id
180 }
181}
182
183impl Eq for Subscription {}
184
185impl PartialOrd for Subscription {
186 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
187 Some(self.cmp(other))
188 }
189}
190
191impl Ord for Subscription {
192 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
193 self.pattern
194 .cmp(&other.pattern)
195 .then_with(|| self.handler_id.cmp(&other.handler_id))
196 }
197}
198
199impl Hash for Subscription {
200 fn hash<H: Hasher>(&self, state: &mut H) {
201 self.pattern.hash(state);
202 self.handler_id.hash(state);
203 }
204}
205
206pub struct MessageBus {
227 pub trader_id: TraderId,
229 pub instance_id: UUID4,
231 pub name: String,
233 pub has_backing: bool,
235 pub(crate) switchboard: MessagingSwitchboard,
236 pub(crate) subscriptions: AHashSet<Subscription>,
237 pub(crate) topics: IndexMap<MStr<Topic>, Vec<Subscription>>,
238 pub(crate) endpoints: IndexMap<MStr<Endpoint>, ShareableMessageHandler>,
239 pub(crate) correlation_index: AHashMap<UUID4, ShareableMessageHandler>,
240 pub(crate) router_quotes: TopicRouter<QuoteTick>,
241 pub(crate) router_trades: TopicRouter<TradeTick>,
242 pub(crate) router_bars: TopicRouter<Bar>,
243 pub(crate) router_deltas: TopicRouter<OrderBookDeltas>,
244 pub(crate) router_depth: TopicRouter<OrderBookDepth>,
245 pub(crate) router_book_snapshots: TopicRouter<OrderBook>,
246 pub(crate) router_mark_prices: TopicRouter<MarkPriceUpdate>,
247 pub(crate) router_index_prices: TopicRouter<IndexPriceUpdate>,
248 pub(crate) router_funding_rates: TopicRouter<FundingRateUpdate>,
249 pub(crate) router_order_events: TopicRouter<OrderEventAny>,
250 pub(crate) router_position_events: TopicRouter<PositionEvent>,
251 pub(crate) router_account_state: TopicRouter<AccountState>,
252 pub(crate) router_orders: TopicRouter<OrderAny>,
253 pub(crate) router_positions: TopicRouter<Position>,
254 pub(crate) router_portfolio: TopicRouter<PortfolioSnapshot>,
255 pub(crate) router_greeks: TopicRouter<GreeksData>,
256 pub(crate) router_option_greeks: TopicRouter<OptionGreeks>,
257 pub(crate) router_option_chain: TopicRouter<OptionChainSlice>,
258 pub(crate) router_instruments: TopicRouter<InstrumentAny>,
259 #[cfg(feature = "defi")]
260 pub(crate) router_defi_blocks: TopicRouter<nautilus_model::defi::Block>, #[cfg(feature = "defi")]
262 pub(crate) router_defi_pools: TopicRouter<nautilus_model::defi::Pool>, #[cfg(feature = "defi")]
264 pub(crate) router_defi_swaps: TopicRouter<nautilus_model::defi::PoolSwap>, #[cfg(feature = "defi")]
266 pub(crate) router_defi_liquidity: TopicRouter<nautilus_model::defi::PoolLiquidityUpdate>, #[cfg(feature = "defi")]
268 pub(crate) router_defi_collects: TopicRouter<nautilus_model::defi::PoolFeeCollect>, #[cfg(feature = "defi")]
270 pub(crate) router_defi_flash: TopicRouter<nautilus_model::defi::PoolFlash>, #[cfg(feature = "defi")]
272 pub(crate) endpoints_defi_data: IntoEndpointMap<nautilus_model::defi::DefiData>, pub(crate) endpoints_quotes: EndpointMap<QuoteTick>,
274 pub(crate) endpoints_trades: EndpointMap<TradeTick>,
275 pub(crate) endpoints_bars: EndpointMap<Bar>,
276 pub(crate) endpoints_account_state: EndpointMap<AccountState>,
277 pub(crate) endpoints_trading_commands: IntoEndpointMap<TradingCommand>,
278 pub(crate) endpoints_data_commands: IntoEndpointMap<DataCommand>,
279 pub(crate) endpoints_data_responses: IntoEndpointMap<DataResponse>,
280 pub(crate) endpoints_exec_reports: IntoEndpointMap<ExecutionReport>,
281 pub(crate) endpoints_order_events: IntoEndpointMap<OrderEventAny>,
282 pub(crate) endpoints_data: IntoEndpointMap<Data>,
283 routers_typed: AHashMap<TypeId, Box<dyn Any>>,
284 endpoints_typed: AHashMap<TypeId, Box<dyn Any>>,
285 sent_count: u64,
286 req_count: u64,
287 res_count: u64,
288 pub_count: u64,
289 external_egress: Option<Rc<RefCell<Box<dyn MessageBusExternalEgress>>>>,
290 has_external_streams: bool,
291 encoding: SerializationEncoding,
292 encoding_market_data: Option<SerializationEncoding>,
293 encoding_builtin: Option<SerializationEncoding>,
294 types_filter: AHashSet<BusPayloadType>,
295 streaming_types: AHashSet<BusPayloadType>,
296}
297
298impl Debug for MessageBus {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 f.debug_struct(stringify!(MessageBus))
301 .field("trader_id", &self.trader_id)
302 .field("instance_id", &self.instance_id)
303 .field("name", &self.name)
304 .field("has_backing", &self.has_backing)
305 .field("external_egress", &self.external_egress.is_some())
306 .finish_non_exhaustive()
307 }
308}
309
310impl Default for MessageBus {
311 fn default() -> Self {
313 Self::new(TraderId::from("TRADER-001"), UUID4::new(), None, None)
314 }
315}
316
317impl MessageBus {
318 #[must_use]
320 pub fn new(
321 trader_id: TraderId,
322 instance_id: UUID4,
323 name: Option<String>,
324 _config: Option<HashMap<String, serde_json::Value>>,
325 ) -> Self {
326 Self {
327 trader_id,
328 instance_id,
329 name: name.unwrap_or(stringify!(MessageBus).to_owned()),
330 switchboard: MessagingSwitchboard::default(),
331 subscriptions: AHashSet::new(),
332 topics: IndexMap::new(),
333 endpoints: IndexMap::new(),
334 correlation_index: AHashMap::new(),
335 has_backing: false,
336 router_quotes: TopicRouter::new(),
337 router_trades: TopicRouter::new(),
338 router_bars: TopicRouter::new(),
339 router_deltas: TopicRouter::new(),
340 router_depth: TopicRouter::new(),
341 router_book_snapshots: TopicRouter::new(),
342 router_mark_prices: TopicRouter::new(),
343 router_index_prices: TopicRouter::new(),
344 router_funding_rates: TopicRouter::new(),
345 router_order_events: TopicRouter::new(),
346 router_position_events: TopicRouter::new(),
347 router_account_state: TopicRouter::new(),
348 router_portfolio: TopicRouter::new(),
349 router_orders: TopicRouter::new(),
350 router_positions: TopicRouter::new(),
351 router_greeks: TopicRouter::new(),
352 router_option_greeks: TopicRouter::new(),
353 router_option_chain: TopicRouter::new(),
354 router_instruments: TopicRouter::new(),
355 #[cfg(feature = "defi")]
356 router_defi_blocks: TopicRouter::new(),
357 #[cfg(feature = "defi")]
358 router_defi_pools: TopicRouter::new(),
359 #[cfg(feature = "defi")]
360 router_defi_swaps: TopicRouter::new(),
361 #[cfg(feature = "defi")]
362 router_defi_liquidity: TopicRouter::new(),
363 #[cfg(feature = "defi")]
364 router_defi_collects: TopicRouter::new(),
365 #[cfg(feature = "defi")]
366 router_defi_flash: TopicRouter::new(),
367 #[cfg(feature = "defi")]
368 endpoints_defi_data: IntoEndpointMap::new(),
369 endpoints_quotes: EndpointMap::new(),
370 endpoints_trades: EndpointMap::new(),
371 endpoints_bars: EndpointMap::new(),
372 endpoints_account_state: EndpointMap::new(),
373 endpoints_trading_commands: IntoEndpointMap::new(),
374 endpoints_data_commands: IntoEndpointMap::new(),
375 endpoints_data_responses: IntoEndpointMap::new(),
376 endpoints_exec_reports: IntoEndpointMap::new(),
377 endpoints_order_events: IntoEndpointMap::new(),
378 endpoints_data: IntoEndpointMap::new(),
379 routers_typed: AHashMap::new(),
380 endpoints_typed: AHashMap::new(),
381 sent_count: 0,
382 req_count: 0,
383 res_count: 0,
384 pub_count: 0,
385 external_egress: None,
386 has_external_streams: false,
387 encoding: SerializationEncoding::Json,
388 encoding_market_data: None,
389 encoding_builtin: None,
390 types_filter: AHashSet::new(),
391 streaming_types: AHashSet::new(),
392 }
393 }
394
395 pub fn register_message_bus(self) -> Rc<RefCell<Self>> {
397 let msgbus = Rc::new(RefCell::new(self));
398 set_message_bus(msgbus.clone());
399 msgbus
400 }
401
402 pub fn router<T: 'static>(&mut self) -> &mut TopicRouter<T> {
408 self.routers_typed
409 .entry(TypeId::of::<T>())
410 .or_insert_with(|| Box::new(TopicRouter::<T>::new()))
411 .downcast_mut::<TopicRouter<T>>()
412 .expect("TopicRouter type mismatch - this is a bug")
413 }
414
415 pub fn endpoint_map<T: 'static>(&mut self) -> &mut EndpointMap<T> {
421 self.endpoints_typed
422 .entry(TypeId::of::<T>())
423 .or_insert_with(|| Box::new(EndpointMap::<T>::new()))
424 .downcast_mut::<EndpointMap<T>>()
425 .expect("EndpointMap type mismatch - this is a bug")
426 }
427
428 pub fn set_external_egress(
432 &mut self,
433 external_egress: Box<dyn MessageBusExternalEgress>,
434 encoding: SerializationEncoding,
435 ) {
436 self.external_egress = Some(Rc::new(RefCell::new(external_egress)));
437 self.has_external_streams = false;
438 self.encoding = encoding;
439 self.encoding_market_data = None;
440 self.encoding_builtin = None;
441 self.has_backing = true;
442 HAS_EXTERNAL_EGRESS.with(|flag| flag.set(true));
443 }
444
445 pub fn set_external_egress_config(
453 &mut self,
454 external_egress: Box<dyn MessageBusExternalEgress>,
455 config: &MessageBusConfig,
456 ) -> crate::config::ConfigResult<()> {
457 config.validate()?;
458
459 self.external_egress = Some(Rc::new(RefCell::new(external_egress)));
460 self.has_external_streams = config
461 .external_streams
462 .as_ref()
463 .is_some_and(|streams| !streams.is_empty());
464 self.encoding = config.encoding;
465 self.encoding_market_data = config.encoding_market_data;
466 self.encoding_builtin = config.encoding_builtin;
467 self.has_backing = true;
468 HAS_EXTERNAL_EGRESS.with(|flag| flag.set(true));
469
470 Ok(())
471 }
472
473 pub fn set_types_filter(&mut self, filter: Vec<String>) {
475 self.types_filter.clear();
476
477 for type_name in filter {
478 let payload_type = BusPayloadType::from_name(&type_name);
479 if !payload_type.as_str().is_empty() {
480 self.types_filter.insert(payload_type);
481 }
482
483 if let Some(payload_type) = BusPayloadType::from_typed_name(&type_name) {
484 self.types_filter.insert(payload_type);
485 }
486 }
487 }
488
489 pub fn add_streaming_type(&mut self, payload_type: BusPayloadType) {
491 if !payload_type.as_str().is_empty() {
492 self.streaming_types.insert(payload_type);
493 }
494 }
495
496 #[must_use]
498 pub fn is_streaming_type(&self, payload_type: BusPayloadType) -> bool {
499 !payload_type.as_str().is_empty() && self.streaming_types.contains(&payload_type)
500 }
501
502 pub fn clear_streaming_types(&mut self) {
504 self.streaming_types.clear();
505 }
506
507 #[must_use]
508 pub(crate) fn has_external_egress(&self) -> bool {
509 self.external_egress.is_some()
510 }
511
512 pub(crate) fn has_external_streams(&self) -> bool {
513 self.has_external_streams
514 }
515
516 pub(crate) fn external_egress(&self) -> Option<Rc<RefCell<Box<dyn MessageBusExternalEgress>>>> {
517 self.external_egress.clone()
518 }
519
520 pub(crate) fn encoding_for(&self, payload_type: BusPayloadType) -> SerializationEncoding {
521 match payload_type.category() {
522 BusPayloadCategory::MarketData => self.encoding_market_data.unwrap_or(self.encoding),
523 BusPayloadCategory::BuiltIn => self.encoding_builtin.unwrap_or(self.encoding),
524 BusPayloadCategory::Other => self.encoding,
525 }
526 }
527
528 pub(crate) fn types_filter(&self) -> &AHashSet<BusPayloadType> {
529 &self.types_filter
530 }
531
532 pub fn dispose(&mut self) {
535 self.subscriptions.clear();
536 self.topics.clear();
537 self.endpoints.clear();
538 self.correlation_index.clear();
539
540 self.router_quotes.clear();
541 self.router_trades.clear();
542 self.router_bars.clear();
543 self.router_deltas.clear();
544 self.router_depth.clear();
545 self.router_book_snapshots.clear();
546 self.router_mark_prices.clear();
547 self.router_index_prices.clear();
548 self.router_funding_rates.clear();
549 self.router_order_events.clear();
550 self.router_position_events.clear();
551 self.router_account_state.clear();
552 self.router_portfolio.clear();
553 self.router_orders.clear();
554 self.router_positions.clear();
555 self.router_greeks.clear();
556 self.router_option_greeks.clear();
557 self.router_option_chain.clear();
558 self.router_instruments.clear();
559
560 #[cfg(feature = "defi")]
561 {
562 self.router_defi_blocks.clear();
563 self.router_defi_pools.clear();
564 self.router_defi_swaps.clear();
565 self.router_defi_liquidity.clear();
566 self.router_defi_collects.clear();
567 self.router_defi_flash.clear();
568 self.endpoints_defi_data.clear();
569 }
570
571 self.endpoints_quotes.clear();
572 self.endpoints_trades.clear();
573 self.endpoints_bars.clear();
574 self.endpoints_account_state.clear();
575 self.endpoints_trading_commands.clear();
576 self.endpoints_data_commands.clear();
577 self.endpoints_data_responses.clear();
578 self.endpoints_exec_reports.clear();
579 self.endpoints_order_events.clear();
580 self.endpoints_data.clear();
581 self.routers_typed.clear();
582 self.endpoints_typed.clear();
583 self.clear_streaming_types();
584 self.sent_count = 0;
585 self.req_count = 0;
586 self.res_count = 0;
587 self.pub_count = 0;
588
589 if let Some(external_egress) = self.external_egress.take() {
590 external_egress.borrow_mut().close();
591 }
592 self.has_external_streams = false;
593 self.has_backing = false;
594 HAS_EXTERNAL_EGRESS.with(|flag| flag.set(false));
595 }
596
597 #[must_use]
599 pub fn mem_address(&self) -> String {
600 format!("{self:p}")
601 }
602
603 #[must_use]
605 pub fn switchboard(&self) -> &MessagingSwitchboard {
606 &self.switchboard
607 }
608
609 #[must_use]
611 pub const fn sent_count(&self) -> u64 {
612 self.sent_count
613 }
614
615 #[must_use]
617 pub const fn req_count(&self) -> u64 {
618 self.req_count
619 }
620
621 #[must_use]
623 pub const fn res_count(&self) -> u64 {
624 self.res_count
625 }
626
627 #[must_use]
629 pub const fn pub_count(&self) -> u64 {
630 self.pub_count
631 }
632
633 pub(crate) fn increment_sent_count(&mut self) {
634 self.sent_count += 1;
635 }
636
637 pub(crate) fn increment_req_count(&mut self) {
638 self.req_count += 1;
639 }
640
641 pub(crate) fn increment_res_count(&mut self) {
642 self.res_count += 1;
643 }
644
645 pub(crate) fn increment_pub_count(&mut self) {
646 self.pub_count += 1;
647 }
648
649 #[must_use]
651 pub fn endpoints(&self) -> Vec<&str> {
652 self.endpoints.iter().map(|e| e.0.as_str()).collect()
653 }
654
655 #[must_use]
657 pub fn patterns(&self) -> Vec<&str> {
658 self.subscriptions
659 .iter()
660 .map(|s| s.pattern.as_str())
661 .collect()
662 }
663
664 pub fn has_subscribers<T: AsRef<str>>(&self, topic: T) -> anyhow::Result<bool> {
670 Ok(self.subscriptions_count(topic)? > 0)
671 }
672
673 pub fn subscriptions_count<T: AsRef<str>>(&self, topic: T) -> anyhow::Result<usize> {
679 let topic = MStr::<Topic>::topic(topic)?;
680 Ok(self
681 .topics
682 .get(&topic)
683 .map_or_else(|| self.find_topic_matches(topic).len(), Vec::len))
684 }
685
686 #[must_use]
688 pub fn subscriptions(&self) -> Vec<&Subscription> {
689 self.subscriptions.iter().collect()
690 }
691
692 #[must_use]
694 pub fn subscription_handler_ids(&self) -> Vec<&str> {
695 self.subscriptions
696 .iter()
697 .map(|s| s.handler_id.as_str())
698 .collect()
699 }
700
701 #[must_use]
707 pub fn is_registered<T: Into<MStr<Endpoint>>>(&self, endpoint: T) -> bool {
708 let endpoint: MStr<Endpoint> = endpoint.into();
709 self.endpoints.contains_key(&endpoint)
710 }
711
712 #[must_use]
714 pub fn is_subscribed<T: AsRef<str>>(
715 &self,
716 pattern: T,
717 handler: ShareableMessageHandler,
718 ) -> bool {
719 let pattern = MStr::<Pattern>::pattern(pattern);
720 let sub = Subscription::new(pattern, handler, None);
721 self.subscriptions.contains(&sub)
722 }
723
724 #[allow(
728 clippy::missing_errors_doc,
729 reason = "closing the in-memory message bus returns no errors"
730 )]
731 pub fn close(&mut self) -> anyhow::Result<()> {
732 if let Some(external_egress) = self.external_egress.take() {
733 external_egress.borrow_mut().close();
734 }
735 self.has_external_streams = false;
736 self.has_backing = false;
737 HAS_EXTERNAL_EGRESS.with(|flag| flag.set(false));
738 Ok(())
739 }
740
741 #[must_use]
743 pub fn get_endpoint(&self, endpoint: MStr<Endpoint>) -> Option<&ShareableMessageHandler> {
744 self.endpoints.get(&endpoint)
745 }
746
747 #[must_use]
749 pub fn get_response_handler(&self, correlation_id: &UUID4) -> Option<&ShareableMessageHandler> {
750 self.correlation_index.get(correlation_id)
751 }
752
753 pub(crate) fn take_response_handler(
755 &mut self,
756 correlation_id: &UUID4,
757 ) -> Option<ShareableMessageHandler> {
758 self.correlation_index.remove(correlation_id)
759 }
760
761 fn find_topic_matches(&self, topic: MStr<Topic>) -> Vec<Subscription> {
763 self.subscriptions
764 .iter()
765 .filter_map(|sub| {
766 if is_matching_backtracking(topic, sub.pattern) {
767 Some(sub.clone())
768 } else {
769 None
770 }
771 })
772 .collect()
773 }
774
775 #[must_use]
778 pub fn matching_subscriptions<T: Into<MStr<Topic>>>(&mut self, topic: T) -> Vec<Subscription> {
779 self.inner_matching_subscriptions(topic.into())
780 }
781
782 fn inner_matching_subscriptions(&mut self, topic: MStr<Topic>) -> Vec<Subscription> {
783 self.topics.get(&topic).cloned().unwrap_or_else(|| {
784 let mut matches = self.find_topic_matches(topic);
785 matches.sort_by(Subscription::delivery_order);
786 self.topics.insert(topic, matches.clone());
787 matches
788 })
789 }
790
791 pub(crate) fn fill_matching_any_handlers(
793 &mut self,
794 topic: MStr<Topic>,
795 buf: &mut SmallVec<[ShareableMessageHandler; 64]>,
796 ) {
797 if let Some(subs) = self.topics.get(&topic) {
798 for sub in subs {
799 buf.push(sub.handler.clone());
800 }
801 } else {
802 let mut matches = self.find_topic_matches(topic);
803 matches.sort_by(Subscription::delivery_order);
804
805 for sub in &matches {
806 buf.push(sub.handler.clone());
807 }
808
809 self.topics.insert(topic, matches);
810 }
811 }
812
813 pub fn register_response_handler(
819 &mut self,
820 correlation_id: &UUID4,
821 handler: ShareableMessageHandler,
822 ) -> anyhow::Result<()> {
823 if self.correlation_index.contains_key(correlation_id) {
824 anyhow::bail!("Correlation ID <{correlation_id}> already has a registered handler");
825 }
826
827 self.correlation_index.insert(*correlation_id, handler);
828
829 Ok(())
830 }
831
832 pub(crate) fn unsubscribe_any(
833 &mut self,
834 pattern: MStr<Pattern>,
835 handler: &ShareableMessageHandler,
836 ) {
837 log::debug!("Unsubscribing {handler:?} from pattern '{pattern}'");
838
839 let handler_id = handler.0.id();
840
841 let count_before = self.subscriptions.len();
842
843 self.topics.values_mut().for_each(|subs| {
844 subs.retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
845 });
846
847 self.subscriptions
848 .retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
849
850 let removed = self.subscriptions.len() < count_before;
851
852 if removed {
853 log::debug!("Handler for pattern '{pattern}' was removed");
854 } else {
855 log::debug!("No matching handler for pattern '{pattern}' was found");
856 }
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use std::{
863 any::Any,
864 cell::RefCell,
865 collections::hash_map::DefaultHasher,
866 fmt::Debug,
867 hash::{Hash, Hasher},
868 rc::Rc,
869 };
870
871 use rand::{RngExt, SeedableRng, rngs::StdRng};
872 use rstest::rstest;
873 use ustr::Ustr;
874
875 use super::*;
876 use crate::msgbus::{
877 self, Handler, ShareableMessageHandler, get_message_bus,
878 matching::is_matching_backtracking,
879 stubs::{get_any_saving_handler, get_call_check_handler, get_stub_shareable_handler},
880 subscriptions_count_any,
881 typed_handler::shareable_handler,
882 };
883
884 #[derive(Debug)]
885 struct RecordingAnyHandler {
886 id: Ustr,
887 label: &'static str,
888 order: Rc<RefCell<Vec<&'static str>>>,
889 }
890
891 impl Handler<dyn Any> for RecordingAnyHandler {
892 fn id(&self) -> Ustr {
893 self.id
894 }
895
896 fn handle(&self, _message: &dyn Any) {
897 self.order.borrow_mut().push(self.label);
898 }
899 }
900
901 fn recording_any_handler(
902 id: &'static str,
903 label: &'static str,
904 order: Rc<RefCell<Vec<&'static str>>>,
905 ) -> ShareableMessageHandler {
906 shareable_handler(Rc::new(RecordingAnyHandler {
907 id: Ustr::from(id),
908 label,
909 order,
910 }))
911 }
912
913 #[rstest]
914 fn test_subscription_ordering_laws() {
915 let handler = get_stub_shareable_handler(Some(Ustr::from("handler-b")));
916 let base = Subscription::new("pattern-b".into(), handler.clone(), Some(1));
917 let different_priority = Subscription::new("pattern-b".into(), handler, Some(2));
918 let different_pattern = Subscription::new(
919 "pattern-a".into(),
920 get_stub_shareable_handler(Some(Ustr::from("handler-b"))),
921 Some(1),
922 );
923 let different_handler = Subscription::new(
924 "pattern-b".into(),
925 get_stub_shareable_handler(Some(Ustr::from("handler-a"))),
926 Some(1),
927 );
928
929 assert_eq!(base, different_priority);
930 assert_eq!(base.cmp(&different_priority), std::cmp::Ordering::Equal);
931
932 let mut base_hasher = DefaultHasher::new();
933 base.hash(&mut base_hasher);
934 let mut different_priority_hasher = DefaultHasher::new();
935 different_priority.hash(&mut different_priority_hasher);
936 assert_eq!(base_hasher.finish(), different_priority_hasher.finish());
937
938 let variants = [
939 base,
940 different_priority,
941 different_pattern,
942 different_handler,
943 ];
944
945 for a in &variants {
946 for b in &variants {
947 assert_eq!(a == b, a.cmp(b).is_eq());
948 assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
949 assert_eq!(a.cmp(b), b.cmp(a).reverse());
950 }
951 }
952 }
953
954 #[rstest]
955 fn test_new() {
956 let trader_id = TraderId::default();
957 let msgbus = MessageBus::new(trader_id, UUID4::new(), None, None);
958
959 assert_eq!(msgbus.trader_id, trader_id);
960 assert_eq!(msgbus.name, stringify!(MessageBus));
961 }
962
963 #[rstest]
964 fn encoding_for_uses_market_data_override() {
965 let msgbus = MessageBus {
966 encoding: SerializationEncoding::Json,
967 encoding_market_data: Some(SerializationEncoding::MsgPack),
968 ..Default::default()
969 };
970
971 assert_eq!(
972 msgbus.encoding_for(BusPayloadType::QuoteTick),
973 SerializationEncoding::MsgPack
974 );
975 assert_eq!(
976 msgbus.encoding_for(BusPayloadType::Custom(Ustr::from("CustomPayload"))),
977 SerializationEncoding::Json
978 );
979 }
980
981 #[rstest]
982 fn encoding_for_uses_builtin_override() {
983 let msgbus = MessageBus {
984 encoding: SerializationEncoding::Json,
985 encoding_builtin: Some(SerializationEncoding::MsgPack),
986 ..Default::default()
987 };
988
989 assert_eq!(
990 msgbus.encoding_for(BusPayloadType::OrderEvent),
991 SerializationEncoding::MsgPack
992 );
993 assert_eq!(
994 msgbus.encoding_for(BusPayloadType::Instrument),
995 SerializationEncoding::Json
996 );
997 }
998
999 #[rstest]
1000 fn encoding_for_uses_default_without_category_override() {
1001 let msgbus = MessageBus {
1002 encoding: SerializationEncoding::MsgPack,
1003 ..Default::default()
1004 };
1005
1006 assert_eq!(
1007 msgbus.encoding_for(BusPayloadType::QuoteTick),
1008 SerializationEncoding::MsgPack
1009 );
1010 assert_eq!(
1011 msgbus.encoding_for(BusPayloadType::OrderEvent),
1012 SerializationEncoding::MsgPack
1013 );
1014 assert_eq!(
1015 msgbus.encoding_for(BusPayloadType::Custom(Ustr::from("CustomPayload"))),
1016 SerializationEncoding::MsgPack
1017 );
1018 }
1019
1020 #[rstest]
1021 fn set_types_filter_resolves_untyped_and_typed_names() {
1022 let mut msgbus = MessageBus::default();
1023
1024 msgbus.set_types_filter(vec![
1025 "QuoteTick".to_string(),
1026 "ExternalCustomPayload".to_string(),
1027 "OrderStatusReport".to_string(),
1028 String::new(),
1029 ]);
1030
1031 let filter = msgbus.types_filter();
1032 assert_eq!(filter.len(), 4);
1033 assert!(filter.contains(&BusPayloadType::QuoteTick));
1034 assert!(filter.contains(&BusPayloadType::Custom(Ustr::from("ExternalCustomPayload"))));
1035 assert!(filter.contains(&BusPayloadType::Custom(Ustr::from("OrderStatusReport"))));
1036 assert!(filter.contains(&BusPayloadType::OrderStatusReport));
1037 assert!(!filter.contains(&BusPayloadType::Custom(Ustr::default())));
1038 }
1039
1040 #[rstest]
1041 fn streaming_type_registration_uses_canonical_payload_names() {
1042 let mut msgbus = MessageBus::default();
1043
1044 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1045 msgbus.add_streaming_type(BusPayloadType::Custom(Ustr::from("CustomPayload")));
1046
1047 assert!(msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1048 assert!(msgbus.is_streaming_type(BusPayloadType::Custom(Ustr::from("CustomPayload"))));
1049 assert!(msgbus.streaming_types.contains(&BusPayloadType::QuoteTick));
1050 assert!(
1051 msgbus
1052 .streaming_types
1053 .contains(&BusPayloadType::Custom(Ustr::from("CustomPayload")))
1054 );
1055 assert!(!msgbus.is_streaming_type(BusPayloadType::TradeTick));
1056 }
1057
1058 #[rstest]
1059 fn streaming_type_registration_ignores_empty_custom_payload_type() {
1060 let mut msgbus = MessageBus::default();
1061
1062 msgbus.add_streaming_type(BusPayloadType::Custom(Ustr::default()));
1063
1064 assert!(!msgbus.is_streaming_type(BusPayloadType::Custom(Ustr::default())));
1065 assert!(msgbus.streaming_types.is_empty());
1066 }
1067
1068 #[rstest]
1069 fn clear_streaming_types_removes_registered_types() {
1070 let mut msgbus = MessageBus::default();
1071 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1072
1073 msgbus.clear_streaming_types();
1074
1075 assert!(!msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1076 }
1077
1078 #[rstest]
1079 fn dispose_clears_streaming_types() {
1080 let mut msgbus = MessageBus::default();
1081 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1082
1083 msgbus.dispose();
1084
1085 assert!(!msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1086 }
1087
1088 #[rstest]
1089 fn test_dispose_resets_counters() {
1090 let mut msgbus = MessageBus::default();
1091
1092 msgbus.increment_sent_count();
1093 msgbus.increment_req_count();
1094 msgbus.increment_res_count();
1095 msgbus.increment_pub_count();
1096 msgbus.dispose();
1097
1098 assert_eq!(msgbus.sent_count(), 0);
1099 assert_eq!(msgbus.req_count(), 0);
1100 assert_eq!(msgbus.res_count(), 0);
1101 assert_eq!(msgbus.pub_count(), 0);
1102 }
1103
1104 #[rstest]
1105 fn test_endpoints_when_no_endpoints() {
1106 let msgbus = get_message_bus();
1107 assert!(msgbus.borrow().endpoints().is_empty());
1108 }
1109
1110 #[rstest]
1111 fn test_topics_when_no_subscriptions() {
1112 let msgbus = get_message_bus();
1113 assert!(msgbus.borrow().patterns().is_empty());
1114 assert!(!msgbus.borrow().has_subscribers("my-topic").unwrap());
1115 }
1116
1117 #[rstest]
1118 fn test_is_subscribed_when_no_subscriptions() {
1119 let msgbus = get_message_bus();
1120 let handler = get_stub_shareable_handler(None);
1121
1122 assert!(!msgbus.borrow().is_subscribed("my-topic", handler));
1123 }
1124
1125 #[rstest]
1126 fn test_get_response_handler_when_no_handler() {
1127 let msgbus = get_message_bus();
1128 let msgbus_ref = msgbus.borrow();
1129 let handler = msgbus_ref.get_response_handler(&UUID4::new());
1130 assert!(handler.is_none());
1131 }
1132
1133 #[rstest]
1134 fn test_get_response_handler_when_already_registered() {
1135 let msgbus = get_message_bus();
1136 let mut msgbus_ref = msgbus.borrow_mut();
1137 let handler = get_stub_shareable_handler(None);
1138
1139 let request_id = UUID4::new();
1140 msgbus_ref
1141 .register_response_handler(&request_id, handler.clone())
1142 .unwrap();
1143
1144 let result = msgbus_ref.register_response_handler(&request_id, handler);
1145 assert!(result.is_err());
1146 }
1147
1148 #[rstest]
1149 fn test_get_response_handler_when_registered() {
1150 let msgbus = get_message_bus();
1151 let mut msgbus_ref = msgbus.borrow_mut();
1152 let handler = get_stub_shareable_handler(None);
1153
1154 let request_id = UUID4::new();
1155 msgbus_ref
1156 .register_response_handler(&request_id, handler)
1157 .unwrap();
1158
1159 let handler = msgbus_ref.get_response_handler(&request_id).unwrap();
1160 assert_eq!(handler.id(), handler.id());
1161 }
1162
1163 #[rstest]
1164 fn test_take_response_handler_removes_registration_and_allows_reregistration() {
1165 let mut msgbus = MessageBus::default();
1166 let request_id = UUID4::new();
1167 let handler = get_stub_shareable_handler(None);
1168 let handler_id = handler.id();
1169 msgbus
1170 .register_response_handler(&request_id, handler)
1171 .unwrap();
1172
1173 let taken = msgbus.take_response_handler(&request_id).unwrap();
1174
1175 assert_eq!(taken.id(), handler_id);
1176 assert!(msgbus.get_response_handler(&request_id).is_none());
1177 assert!(msgbus.register_response_handler(&request_id, taken).is_ok());
1178 }
1179
1180 #[rstest]
1181 fn test_is_registered_when_no_registrations() {
1182 let msgbus = get_message_bus();
1183 assert!(!msgbus.borrow().is_registered("MyEndpoint"));
1184 }
1185
1186 #[rstest]
1187 fn test_register_endpoint() {
1188 let msgbus = get_message_bus();
1189 let endpoint = "MyEndpoint".into();
1190 let handler = get_stub_shareable_handler(None);
1191
1192 msgbus::register_any(endpoint, handler);
1193
1194 assert_eq!(msgbus.borrow().endpoints(), vec![endpoint.to_string()]);
1195 assert!(msgbus.borrow().get_endpoint(endpoint).is_some());
1196 }
1197
1198 #[rstest]
1199 fn test_endpoint_send() {
1200 let msgbus = get_message_bus();
1201 let endpoint = "MyEndpoint".into();
1202 let (handler, checker) = get_call_check_handler(None);
1203 let sent_count = msgbus.borrow().sent_count();
1204
1205 msgbus::register_any(endpoint, handler);
1206 assert!(msgbus.borrow().get_endpoint(endpoint).is_some());
1207 assert!(!checker.was_called());
1208
1209 msgbus::send_any(endpoint, &"Test Message");
1210
1211 assert!(checker.was_called());
1212 assert_eq!(msgbus.borrow().sent_count(), sent_count + 1);
1213 }
1214
1215 #[rstest]
1216 fn test_endpoint_send_value_increments_sent_count() {
1217 let msgbus = get_message_bus();
1218 let endpoint = "MyValueEndpoint".into();
1219 let (handler, checker) = get_call_check_handler(None);
1220 let sent_count = msgbus.borrow().sent_count();
1221
1222 msgbus::register_any(endpoint, handler);
1223 msgbus::send_any_value(endpoint, &"Test Message");
1224
1225 assert!(checker.was_called());
1226 assert_eq!(msgbus.borrow().sent_count(), sent_count + 1);
1227 }
1228
1229 #[rstest]
1230 fn test_publish_any_increments_publish_count() {
1231 let msgbus = get_message_bus();
1232 let topic = "my-published-topic";
1233 let (handler, checker) = get_call_check_handler(None);
1234 let pub_count = msgbus.borrow().pub_count();
1235
1236 msgbus::subscribe_any(topic.into(), handler, None);
1237 msgbus::publish_any(topic.into(), &"Test Message");
1238
1239 assert!(checker.was_called());
1240 assert_eq!(msgbus.borrow().pub_count(), pub_count + 1);
1241 }
1242
1243 #[rstest]
1244 fn test_deregsiter_endpoint() {
1245 let msgbus = get_message_bus();
1246 let endpoint = "MyEndpoint".into();
1247 let handler = get_stub_shareable_handler(None);
1248
1249 msgbus::register_any(endpoint, handler);
1250 msgbus::deregister_any(endpoint);
1251
1252 assert!(msgbus.borrow().endpoints().is_empty());
1253 }
1254
1255 #[rstest]
1256 fn test_subscribe() {
1257 let msgbus = get_message_bus();
1258 let topic = "my-topic";
1259 let handler = get_stub_shareable_handler(None);
1260
1261 msgbus::subscribe_any(topic.into(), handler, Some(1));
1262
1263 assert!(msgbus.borrow().has_subscribers(topic).unwrap());
1264 assert_eq!(msgbus.borrow().patterns(), vec![topic]);
1265 }
1266
1267 #[rstest]
1268 fn test_unsubscribe() {
1269 let msgbus = get_message_bus();
1270 let topic = "my-topic";
1271 let handler = get_stub_shareable_handler(None);
1272
1273 msgbus::subscribe_any(topic.into(), handler.clone(), None);
1274 msgbus::unsubscribe_any(topic.into(), &handler);
1275
1276 assert!(!msgbus.borrow().has_subscribers(topic).unwrap());
1277 assert!(msgbus.borrow().patterns().is_empty());
1278 }
1279
1280 #[rstest]
1281 fn test_subscriptions_count_rejects_invalid_topic() {
1282 let msgbus = get_message_bus();
1283
1284 let err = msgbus
1285 .borrow()
1286 .subscriptions_count("data.*")
1287 .expect_err("wildcards are invalid in topics");
1288
1289 assert_eq!(
1290 err.to_string(),
1291 "Topic `value` contained invalid characters, was data.*"
1292 );
1293 }
1294
1295 #[rstest]
1296 fn test_has_subscribers_rejects_invalid_topic() {
1297 let msgbus = get_message_bus();
1298
1299 let err = msgbus
1300 .borrow()
1301 .has_subscribers("data.*")
1302 .expect_err("wildcards are invalid in topics");
1303
1304 assert_eq!(
1305 err.to_string(),
1306 "Topic `value` contained invalid characters, was data.*"
1307 );
1308 }
1309
1310 #[rstest]
1311 fn test_subscriptions_count_any_rejects_invalid_topic() {
1312 let err = subscriptions_count_any("data.*").expect_err("wildcards are invalid in topics");
1313
1314 assert_eq!(
1315 err.to_string(),
1316 "Topic `value` contained invalid characters, was data.*"
1317 );
1318 }
1319
1320 #[rstest]
1321 fn test_matching_subscriptions() {
1322 let msgbus = get_message_bus();
1323 let pattern = "my-pattern";
1324
1325 let handler_id1 = Ustr::from("1");
1326 let handler1 = get_stub_shareable_handler(Some(handler_id1));
1327
1328 let handler_id2 = Ustr::from("2");
1329 let handler2 = get_stub_shareable_handler(Some(handler_id2));
1330
1331 let handler_id3 = Ustr::from("3");
1332 let handler3 = get_stub_shareable_handler(Some(handler_id3));
1333
1334 let handler_id4 = Ustr::from("4");
1335 let handler4 = get_stub_shareable_handler(Some(handler_id4));
1336
1337 msgbus::subscribe_any(pattern.into(), handler1, None);
1338 msgbus::subscribe_any(pattern.into(), handler2, None);
1339 msgbus::subscribe_any(pattern.into(), handler3, Some(1));
1340 msgbus::subscribe_any(pattern.into(), handler4, Some(2));
1341
1342 assert_eq!(
1343 msgbus.borrow().patterns(),
1344 vec![pattern, pattern, pattern, pattern]
1345 );
1346 assert_eq!(subscriptions_count_any(pattern).unwrap(), 4);
1347
1348 let topic = pattern;
1349 let subs = msgbus.borrow_mut().matching_subscriptions(topic);
1350 assert_eq!(subs.len(), 4);
1351 assert_eq!(subs[0].handler_id, handler_id4);
1352 assert_eq!(subs[1].handler_id, handler_id3);
1353 assert_eq!(subs[2].handler_id, handler_id1);
1354 assert_eq!(subs[3].handler_id, handler_id2);
1355 }
1356
1357 #[rstest]
1358 fn test_matching_subscriptions_orders_by_full_delivery_key_on_cache_miss() {
1359 MessageBus::default().register_message_bus();
1360 let order = Rc::new(RefCell::new(Vec::new()));
1361
1362 msgbus::subscribe_any(
1363 "delivery.*".into(),
1364 recording_any_handler("handler-z", "low-z", order.clone()),
1365 Some(1),
1366 );
1367 msgbus::subscribe_any(
1368 "delivery.topic".into(),
1369 recording_any_handler("handler-b", "exact-b", order.clone()),
1370 Some(10),
1371 );
1372 msgbus::subscribe_any(
1373 "delivery.topic".into(),
1374 recording_any_handler("handler-a", "exact-a", order.clone()),
1375 Some(10),
1376 );
1377 msgbus::subscribe_any(
1378 "delivery.*".into(),
1379 recording_any_handler("handler-b", "wildcard-b", order),
1380 Some(10),
1381 );
1382
1383 let subscriptions = get_message_bus()
1384 .borrow_mut()
1385 .matching_subscriptions("delivery.topic");
1386 let actual = subscriptions
1387 .iter()
1388 .map(|sub| (sub.priority, sub.pattern.as_str(), sub.handler_id.as_str()))
1389 .collect::<Vec<_>>();
1390
1391 assert_eq!(
1392 actual,
1393 vec![
1394 (10, "delivery.*", "handler-b"),
1395 (10, "delivery.topic", "handler-a"),
1396 (10, "delivery.topic", "handler-b"),
1397 (1, "delivery.*", "handler-z"),
1398 ]
1399 );
1400 }
1401
1402 #[rstest]
1403 fn test_first_uncached_publish_any_orders_by_full_delivery_key() {
1404 MessageBus::default().register_message_bus();
1405 let order = Rc::new(RefCell::new(Vec::new()));
1406
1407 msgbus::subscribe_any(
1408 "delivery.*".into(),
1409 recording_any_handler("handler-z", "low-z", order.clone()),
1410 Some(1),
1411 );
1412 msgbus::subscribe_any(
1413 "delivery.topic".into(),
1414 recording_any_handler("handler-b", "exact-b", order.clone()),
1415 Some(10),
1416 );
1417 msgbus::subscribe_any(
1418 "delivery.topic".into(),
1419 recording_any_handler("handler-a", "exact-a", order.clone()),
1420 Some(10),
1421 );
1422 msgbus::subscribe_any(
1423 "delivery.*".into(),
1424 recording_any_handler("handler-b", "wildcard-b", order.clone()),
1425 Some(10),
1426 );
1427
1428 msgbus::publish_any("delivery.topic".into(), &());
1429
1430 assert_eq!(
1431 *order.borrow(),
1432 vec!["wildcard-b", "exact-a", "exact-b", "low-z"]
1433 );
1434 }
1435
1436 #[rstest]
1437 fn test_late_subscription_orders_cached_any_topic_by_full_delivery_key() {
1438 MessageBus::default().register_message_bus();
1439 let order = Rc::new(RefCell::new(Vec::new()));
1440
1441 msgbus::subscribe_any(
1442 "delivery.*".into(),
1443 recording_any_handler("handler-z", "low-z", order.clone()),
1444 Some(1),
1445 );
1446 msgbus::subscribe_any(
1447 "delivery.topic".into(),
1448 recording_any_handler("handler-b", "exact-b", order.clone()),
1449 Some(10),
1450 );
1451 msgbus::subscribe_any(
1452 "delivery.topic".into(),
1453 recording_any_handler("handler-a", "exact-a", order.clone()),
1454 Some(10),
1455 );
1456 msgbus::publish_any("delivery.topic".into(), &());
1457 order.borrow_mut().clear();
1458
1459 msgbus::subscribe_any(
1460 "delivery.*".into(),
1461 recording_any_handler("handler-b", "wildcard-b", order.clone()),
1462 Some(10),
1463 );
1464 msgbus::publish_any("delivery.topic".into(), &());
1465
1466 assert_eq!(
1467 *order.borrow(),
1468 vec!["wildcard-b", "exact-a", "exact-b", "low-z"]
1469 );
1470 }
1471
1472 #[rstest]
1473 fn test_subscription_pattern_matching() {
1474 let msgbus = get_message_bus();
1475 let handler1 = get_stub_shareable_handler(Some(Ustr::from("1")));
1476 let handler2 = get_stub_shareable_handler(Some(Ustr::from("2")));
1477 let handler3 = get_stub_shareable_handler(Some(Ustr::from("3")));
1478
1479 msgbus::subscribe_any("data.quotes.*".into(), handler1, None);
1480 msgbus::subscribe_any("data.trades.*".into(), handler2, None);
1481 msgbus::subscribe_any("data.*.BINANCE.*".into(), handler3, None);
1482 assert_eq!(msgbus.borrow().subscriptions().len(), 3);
1483
1484 let topic = "data.quotes.BINANCE.ETHUSDT";
1485 assert_eq!(msgbus.borrow().find_topic_matches(topic.into()).len(), 2);
1486
1487 let matches = msgbus.borrow_mut().matching_subscriptions(topic);
1488 assert_eq!(matches.len(), 2);
1489 assert_eq!(matches[0].handler_id, Ustr::from("3"));
1490 assert_eq!(matches[1].handler_id, Ustr::from("1"));
1491 }
1492
1493 #[rstest]
1494 fn test_late_wildcard_subscription_receives_cached_topic() {
1495 let msgbus = get_message_bus();
1496 let topic = "data.instrument.POLYMARKET.TEST-SYMBOL";
1497
1498 let (early_handler, early_saver) =
1499 get_any_saving_handler::<String>(Some(Ustr::from("early")));
1500 msgbus::subscribe_any("data.*.POLYMARKET.*".into(), early_handler, None);
1501
1502 msgbus::publish_any(topic.into(), &"ONE".to_string());
1503
1504 let (late_handler, late_saver) = get_any_saving_handler::<String>(Some(Ustr::from("late")));
1505 msgbus::subscribe_any("data.instrument.POLYMARKET.*".into(), late_handler, None);
1506
1507 msgbus::publish_any(topic.into(), &"TWO".to_string());
1508
1509 assert_eq!(early_saver.get_messages(), vec!["ONE", "TWO"]);
1510 assert_eq!(late_saver.get_messages(), vec!["TWO"]);
1511
1512 let topic_mstr: MStr<Topic> = topic.into();
1513 let cached = msgbus.borrow_mut().matching_subscriptions(topic_mstr);
1514 assert_eq!(cached.len(), 2);
1515 }
1516
1517 #[rstest]
1518 fn test_late_wildcard_backfills_into_multiple_cached_topics() {
1519 let msgbus = get_message_bus();
1520 let topics = ["data.A", "data.B", "data.C"];
1521
1522 let (early_handler, early_saver) =
1523 get_any_saving_handler::<String>(Some(Ustr::from("early")));
1524 msgbus::subscribe_any("data.*".into(), early_handler, None);
1525
1526 for topic in &topics {
1527 msgbus::publish_any((*topic).into(), &(*topic).to_string());
1528 }
1529
1530 let (late_handler, late_saver) = get_any_saving_handler::<String>(Some(Ustr::from("late")));
1531 msgbus::subscribe_any("data.*".into(), late_handler, None);
1532
1533 for topic in &topics {
1534 msgbus::publish_any((*topic).into(), &format!("{topic}-2"));
1535 }
1536
1537 assert_eq!(
1538 early_saver.get_messages(),
1539 vec![
1540 "data.A", "data.B", "data.C", "data.A-2", "data.B-2", "data.C-2"
1541 ],
1542 );
1543 assert_eq!(
1544 late_saver.get_messages(),
1545 vec!["data.A-2", "data.B-2", "data.C-2"]
1546 );
1547
1548 for topic in &topics {
1549 let topic_mstr: MStr<Topic> = (*topic).into();
1550 assert_eq!(
1551 msgbus.borrow_mut().matching_subscriptions(topic_mstr).len(),
1552 2,
1553 "topic {topic} should have both subscribers cached",
1554 );
1555 }
1556 }
1557
1558 struct SimpleSubscriptionModel {
1560 subscriptions: Vec<(String, String)>,
1562 }
1563
1564 impl SimpleSubscriptionModel {
1565 fn new() -> Self {
1566 Self {
1567 subscriptions: Vec::new(),
1568 }
1569 }
1570
1571 fn subscribe(&mut self, pattern: &str, handler_id: &str) {
1572 let subscription = (pattern.to_string(), handler_id.to_string());
1573 if !self.subscriptions.contains(&subscription) {
1574 self.subscriptions.push(subscription);
1575 }
1576 }
1577
1578 fn unsubscribe(&mut self, pattern: &str, handler_id: &str) -> bool {
1579 let subscription = (pattern.to_string(), handler_id.to_string());
1580 if let Some(idx) = self.subscriptions.iter().position(|s| s == &subscription) {
1581 self.subscriptions.remove(idx);
1582 true
1583 } else {
1584 false
1585 }
1586 }
1587
1588 fn is_subscribed(&self, pattern: &str, handler_id: &str) -> bool {
1589 self.subscriptions
1590 .contains(&(pattern.to_string(), handler_id.to_string()))
1591 }
1592
1593 fn matching_subscriptions(&self, topic: &str) -> Vec<(String, String)> {
1594 let topic = topic.into();
1595
1596 self.subscriptions
1597 .iter()
1598 .filter(|(pat, _)| is_matching_backtracking(topic, pat.into()))
1599 .map(|(pat, id)| (pat.clone(), id.clone()))
1600 .collect()
1601 }
1602
1603 fn subscription_count(&self) -> usize {
1604 self.subscriptions.len()
1605 }
1606 }
1607
1608 #[rstest]
1609 fn subscription_model_fuzz_testing() {
1610 let mut rng = StdRng::seed_from_u64(42);
1611
1612 let msgbus = get_message_bus();
1613 let mut model = SimpleSubscriptionModel::new();
1614
1615 let mut handlers: Vec<(String, ShareableMessageHandler)> = Vec::new();
1617
1618 let patterns = generate_test_patterns(&mut rng);
1620
1621 let handler_ids: Vec<String> = (0..50).map(|i| format!("handler_{i}")).collect();
1623
1624 for id in &handler_ids {
1626 let handler = get_stub_shareable_handler(Some(Ustr::from(id)));
1627 handlers.push((id.clone(), handler));
1628 }
1629
1630 let num_operations = 50_000;
1631 for op_num in 0..num_operations {
1632 let operation = rng.random_range(0..4);
1633
1634 match operation {
1635 0 => {
1637 let pattern_idx = rng.random_range(0..patterns.len());
1638 let handler_idx = rng.random_range(0..handlers.len());
1639 let pattern = &patterns[pattern_idx];
1640 let (handler_id, handler) = &handlers[handler_idx];
1641
1642 model.subscribe(pattern, handler_id);
1644
1645 msgbus::subscribe_any(pattern.as_str().into(), handler.clone(), None);
1647
1648 assert_eq!(
1649 model.subscription_count(),
1650 msgbus.borrow().subscriptions().len()
1651 );
1652
1653 assert!(
1654 msgbus.borrow().is_subscribed(pattern, handler.clone()),
1655 "Op {op_num}: is_subscribed should return true after subscribe"
1656 );
1657 }
1658
1659 1 => {
1661 if model.subscription_count() > 0 {
1662 let sub_idx = rng.random_range(0..model.subscription_count());
1663 let (pattern, handler_id) = model.subscriptions[sub_idx].clone();
1664
1665 model.unsubscribe(&pattern, &handler_id);
1667
1668 let handler = handlers
1670 .iter()
1671 .find(|(id, _)| id == &handler_id)
1672 .map(|(_, h)| h.clone())
1673 .unwrap();
1674
1675 msgbus::unsubscribe_any(pattern.as_str().into(), &handler);
1677
1678 assert_eq!(
1679 model.subscription_count(),
1680 msgbus.borrow().subscriptions().len()
1681 );
1682 assert!(
1683 !msgbus.borrow().is_subscribed(pattern, handler.clone()),
1684 "Op {op_num}: is_subscribed should return false after unsubscribe"
1685 );
1686 }
1687 }
1688
1689 2 => {
1691 let pattern_idx = rng.random_range(0..patterns.len());
1693 let handler_idx = rng.random_range(0..handlers.len());
1694 let pattern = &patterns[pattern_idx];
1695 let (handler_id, handler) = &handlers[handler_idx];
1696
1697 let expected = model.is_subscribed(pattern, handler_id);
1698 let actual = msgbus.borrow().is_subscribed(pattern, handler.clone());
1699
1700 assert_eq!(
1701 expected, actual,
1702 "Op {op_num}: Subscription state mismatch for pattern '{pattern}', handler '{handler_id}': expected={expected}, actual={actual}"
1703 );
1704 }
1705
1706 3 => {
1708 let topic = create_topic(&mut rng);
1710
1711 let actual_matches = msgbus.borrow_mut().matching_subscriptions(topic);
1712 let expected_matches = model.matching_subscriptions(&topic);
1713
1714 assert_eq!(
1715 expected_matches.len(),
1716 actual_matches.len(),
1717 "Op {}: Match count mismatch for topic '{}': expected={}, actual={}",
1718 op_num,
1719 topic,
1720 expected_matches.len(),
1721 actual_matches.len()
1722 );
1723
1724 for sub in &actual_matches {
1725 assert!(
1726 expected_matches
1727 .contains(&(sub.pattern.to_string(), sub.handler_id.to_string())),
1728 "Op {}: Expected match not found: pattern='{}', handler_id='{}'",
1729 op_num,
1730 sub.pattern,
1731 sub.handler_id
1732 );
1733 }
1734 }
1735 _ => unreachable!(),
1736 }
1737 }
1738 }
1739
1740 fn generate_pattern_from_topic(topic: &str, rng: &mut StdRng) -> String {
1741 let mut pattern = String::new();
1742
1743 for c in topic.chars() {
1744 let val: f64 = rng.random();
1745 if val < 0.1 {
1746 pattern.push('*');
1747 } else if val < 0.3 {
1748 pattern.push('?');
1749 } else if val >= 0.5 {
1750 pattern.push(c);
1751 }
1752 }
1753
1754 pattern
1755 }
1756
1757 fn generate_test_patterns(rng: &mut StdRng) -> Vec<String> {
1758 let mut patterns = vec![
1759 "data.*.*.*".to_string(),
1760 "*.*.BINANCE.*".to_string(),
1761 "events.order.*".to_string(),
1762 "data.*.*.?USDT".to_string(),
1763 "*.trades.*.BTC*".to_string(),
1764 "*.*.*.*".to_string(),
1765 ];
1766
1767 for _ in 0..50 {
1769 match rng.random_range(0..10) {
1770 0..=1 => {
1772 let idx = rng.random_range(0..patterns.len());
1773 patterns.push(patterns[idx].clone());
1774 }
1775 _ => {
1777 let topic = create_topic(rng);
1778 let pattern = generate_pattern_from_topic(&topic, rng);
1779 patterns.push(pattern);
1780 }
1781 }
1782 }
1783
1784 patterns
1785 }
1786
1787 fn create_topic(rng: &mut StdRng) -> Ustr {
1788 let cat = ["data", "info", "order"];
1789 let model = ["quotes", "trades", "orderbooks", "depths"];
1790 let venue = ["BINANCE", "BYBIT", "OKX", "FTX", "KRAKEN"];
1791 let instrument = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "XRPUSDT", "DOGEUSDT"];
1792
1793 let cat = cat[rng.random_range(0..cat.len())];
1794 let model = model[rng.random_range(0..model.len())];
1795 let venue = venue[rng.random_range(0..venue.len())];
1796 let instrument = instrument[rng.random_range(0..instrument.len())];
1797 Ustr::from(&format!("{cat}.{model}.{venue}.{instrument}"))
1798 }
1799}