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 pub fn close(&mut self) -> anyhow::Result<()> {
730 if let Some(external_egress) = self.external_egress.take() {
731 external_egress.borrow_mut().close();
732 }
733 self.has_external_streams = false;
734 self.has_backing = false;
735 HAS_EXTERNAL_EGRESS.with(|flag| flag.set(false));
736 Ok(())
737 }
738
739 #[must_use]
741 pub fn get_endpoint(&self, endpoint: MStr<Endpoint>) -> Option<&ShareableMessageHandler> {
742 self.endpoints.get(&endpoint)
743 }
744
745 #[must_use]
747 pub fn get_response_handler(&self, correlation_id: &UUID4) -> Option<&ShareableMessageHandler> {
748 self.correlation_index.get(correlation_id)
749 }
750
751 pub(crate) fn take_response_handler(
753 &mut self,
754 correlation_id: &UUID4,
755 ) -> Option<ShareableMessageHandler> {
756 self.correlation_index.remove(correlation_id)
757 }
758
759 fn find_topic_matches(&self, topic: MStr<Topic>) -> Vec<Subscription> {
761 self.subscriptions
762 .iter()
763 .filter_map(|sub| {
764 if is_matching_backtracking(topic, sub.pattern) {
765 Some(sub.clone())
766 } else {
767 None
768 }
769 })
770 .collect()
771 }
772
773 #[must_use]
776 pub fn matching_subscriptions<T: Into<MStr<Topic>>>(&mut self, topic: T) -> Vec<Subscription> {
777 self.inner_matching_subscriptions(topic.into())
778 }
779
780 fn inner_matching_subscriptions(&mut self, topic: MStr<Topic>) -> Vec<Subscription> {
781 self.topics.get(&topic).cloned().unwrap_or_else(|| {
782 let mut matches = self.find_topic_matches(topic);
783 matches.sort_by(Subscription::delivery_order);
784 self.topics.insert(topic, matches.clone());
785 matches
786 })
787 }
788
789 pub(crate) fn fill_matching_any_handlers(
791 &mut self,
792 topic: MStr<Topic>,
793 buf: &mut SmallVec<[ShareableMessageHandler; 64]>,
794 ) {
795 if let Some(subs) = self.topics.get(&topic) {
796 for sub in subs {
797 buf.push(sub.handler.clone());
798 }
799 } else {
800 let mut matches = self.find_topic_matches(topic);
801 matches.sort_by(Subscription::delivery_order);
802
803 for sub in &matches {
804 buf.push(sub.handler.clone());
805 }
806
807 self.topics.insert(topic, matches);
808 }
809 }
810
811 pub fn register_response_handler(
817 &mut self,
818 correlation_id: &UUID4,
819 handler: ShareableMessageHandler,
820 ) -> anyhow::Result<()> {
821 if self.correlation_index.contains_key(correlation_id) {
822 anyhow::bail!("Correlation ID <{correlation_id}> already has a registered handler");
823 }
824
825 self.correlation_index.insert(*correlation_id, handler);
826
827 Ok(())
828 }
829
830 pub(crate) fn unsubscribe_any(
831 &mut self,
832 pattern: MStr<Pattern>,
833 handler: &ShareableMessageHandler,
834 ) {
835 log::debug!("Unsubscribing {handler:?} from pattern '{pattern}'");
836
837 let handler_id = handler.0.id();
838
839 let count_before = self.subscriptions.len();
840
841 self.topics.values_mut().for_each(|subs| {
842 subs.retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
843 });
844
845 self.subscriptions
846 .retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
847
848 let removed = self.subscriptions.len() < count_before;
849
850 if removed {
851 log::debug!("Handler for pattern '{pattern}' was removed");
852 } else {
853 log::debug!("No matching handler for pattern '{pattern}' was found");
854 }
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use std::{
861 any::Any,
862 cell::RefCell,
863 collections::hash_map::DefaultHasher,
864 fmt::Debug,
865 hash::{Hash, Hasher},
866 rc::Rc,
867 };
868
869 use rand::{RngExt, SeedableRng, rngs::StdRng};
870 use rstest::rstest;
871 use ustr::Ustr;
872
873 use super::*;
874 use crate::msgbus::{
875 self, Handler, ShareableMessageHandler, get_message_bus,
876 matching::is_matching_backtracking,
877 stubs::{get_any_saving_handler, get_call_check_handler, get_stub_shareable_handler},
878 subscriptions_count_any,
879 typed_handler::shareable_handler,
880 };
881
882 #[derive(Debug)]
883 struct RecordingAnyHandler {
884 id: Ustr,
885 label: &'static str,
886 order: Rc<RefCell<Vec<&'static str>>>,
887 }
888
889 impl Handler<dyn Any> for RecordingAnyHandler {
890 fn id(&self) -> Ustr {
891 self.id
892 }
893
894 fn handle(&self, _message: &dyn Any) {
895 self.order.borrow_mut().push(self.label);
896 }
897 }
898
899 fn recording_any_handler(
900 id: &'static str,
901 label: &'static str,
902 order: Rc<RefCell<Vec<&'static str>>>,
903 ) -> ShareableMessageHandler {
904 shareable_handler(Rc::new(RecordingAnyHandler {
905 id: Ustr::from(id),
906 label,
907 order,
908 }))
909 }
910
911 #[rstest]
912 fn test_subscription_ordering_laws() {
913 let handler = get_stub_shareable_handler(Some(Ustr::from("handler-b")));
914 let base = Subscription::new("pattern-b".into(), handler.clone(), Some(1));
915 let different_priority = Subscription::new("pattern-b".into(), handler, Some(2));
916 let different_pattern = Subscription::new(
917 "pattern-a".into(),
918 get_stub_shareable_handler(Some(Ustr::from("handler-b"))),
919 Some(1),
920 );
921 let different_handler = Subscription::new(
922 "pattern-b".into(),
923 get_stub_shareable_handler(Some(Ustr::from("handler-a"))),
924 Some(1),
925 );
926
927 assert_eq!(base, different_priority);
928 assert_eq!(base.cmp(&different_priority), std::cmp::Ordering::Equal);
929
930 let mut base_hasher = DefaultHasher::new();
931 base.hash(&mut base_hasher);
932 let mut different_priority_hasher = DefaultHasher::new();
933 different_priority.hash(&mut different_priority_hasher);
934 assert_eq!(base_hasher.finish(), different_priority_hasher.finish());
935
936 let variants = [
937 base,
938 different_priority,
939 different_pattern,
940 different_handler,
941 ];
942
943 for a in &variants {
944 for b in &variants {
945 assert_eq!(a == b, a.cmp(b).is_eq());
946 assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
947 assert_eq!(a.cmp(b), b.cmp(a).reverse());
948 }
949 }
950 }
951
952 #[rstest]
953 fn test_new() {
954 let trader_id = TraderId::default();
955 let msgbus = MessageBus::new(trader_id, UUID4::new(), None, None);
956
957 assert_eq!(msgbus.trader_id, trader_id);
958 assert_eq!(msgbus.name, stringify!(MessageBus));
959 }
960
961 #[rstest]
962 fn encoding_for_uses_market_data_override() {
963 let msgbus = MessageBus {
964 encoding: SerializationEncoding::Json,
965 encoding_market_data: Some(SerializationEncoding::MsgPack),
966 ..Default::default()
967 };
968
969 assert_eq!(
970 msgbus.encoding_for(BusPayloadType::QuoteTick),
971 SerializationEncoding::MsgPack
972 );
973 assert_eq!(
974 msgbus.encoding_for(BusPayloadType::Custom(Ustr::from("CustomPayload"))),
975 SerializationEncoding::Json
976 );
977 }
978
979 #[rstest]
980 fn encoding_for_uses_builtin_override() {
981 let msgbus = MessageBus {
982 encoding: SerializationEncoding::Json,
983 encoding_builtin: Some(SerializationEncoding::MsgPack),
984 ..Default::default()
985 };
986
987 assert_eq!(
988 msgbus.encoding_for(BusPayloadType::OrderEvent),
989 SerializationEncoding::MsgPack
990 );
991 assert_eq!(
992 msgbus.encoding_for(BusPayloadType::Instrument),
993 SerializationEncoding::Json
994 );
995 }
996
997 #[rstest]
998 fn encoding_for_uses_default_without_category_override() {
999 let msgbus = MessageBus {
1000 encoding: SerializationEncoding::MsgPack,
1001 ..Default::default()
1002 };
1003
1004 assert_eq!(
1005 msgbus.encoding_for(BusPayloadType::QuoteTick),
1006 SerializationEncoding::MsgPack
1007 );
1008 assert_eq!(
1009 msgbus.encoding_for(BusPayloadType::OrderEvent),
1010 SerializationEncoding::MsgPack
1011 );
1012 assert_eq!(
1013 msgbus.encoding_for(BusPayloadType::Custom(Ustr::from("CustomPayload"))),
1014 SerializationEncoding::MsgPack
1015 );
1016 }
1017
1018 #[rstest]
1019 fn set_types_filter_resolves_untyped_and_typed_names() {
1020 let mut msgbus = MessageBus::default();
1021
1022 msgbus.set_types_filter(vec![
1023 "QuoteTick".to_string(),
1024 "ExternalCustomPayload".to_string(),
1025 "OrderStatusReport".to_string(),
1026 String::new(),
1027 ]);
1028
1029 let filter = msgbus.types_filter();
1030 assert_eq!(filter.len(), 4);
1031 assert!(filter.contains(&BusPayloadType::QuoteTick));
1032 assert!(filter.contains(&BusPayloadType::Custom(Ustr::from("ExternalCustomPayload"))));
1033 assert!(filter.contains(&BusPayloadType::Custom(Ustr::from("OrderStatusReport"))));
1034 assert!(filter.contains(&BusPayloadType::OrderStatusReport));
1035 assert!(!filter.contains(&BusPayloadType::Custom(Ustr::default())));
1036 }
1037
1038 #[rstest]
1039 fn streaming_type_registration_uses_canonical_payload_names() {
1040 let mut msgbus = MessageBus::default();
1041
1042 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1043 msgbus.add_streaming_type(BusPayloadType::Custom(Ustr::from("CustomPayload")));
1044
1045 assert!(msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1046 assert!(msgbus.is_streaming_type(BusPayloadType::Custom(Ustr::from("CustomPayload"))));
1047 assert!(msgbus.streaming_types.contains(&BusPayloadType::QuoteTick));
1048 assert!(
1049 msgbus
1050 .streaming_types
1051 .contains(&BusPayloadType::Custom(Ustr::from("CustomPayload")))
1052 );
1053 assert!(!msgbus.is_streaming_type(BusPayloadType::TradeTick));
1054 }
1055
1056 #[rstest]
1057 fn streaming_type_registration_ignores_empty_custom_payload_type() {
1058 let mut msgbus = MessageBus::default();
1059
1060 msgbus.add_streaming_type(BusPayloadType::Custom(Ustr::default()));
1061
1062 assert!(!msgbus.is_streaming_type(BusPayloadType::Custom(Ustr::default())));
1063 assert!(msgbus.streaming_types.is_empty());
1064 }
1065
1066 #[rstest]
1067 fn clear_streaming_types_removes_registered_types() {
1068 let mut msgbus = MessageBus::default();
1069 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1070
1071 msgbus.clear_streaming_types();
1072
1073 assert!(!msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1074 }
1075
1076 #[rstest]
1077 fn dispose_clears_streaming_types() {
1078 let mut msgbus = MessageBus::default();
1079 msgbus.add_streaming_type(BusPayloadType::QuoteTick);
1080
1081 msgbus.dispose();
1082
1083 assert!(!msgbus.is_streaming_type(BusPayloadType::QuoteTick));
1084 }
1085
1086 #[rstest]
1087 fn test_dispose_resets_counters() {
1088 let mut msgbus = MessageBus::default();
1089
1090 msgbus.increment_sent_count();
1091 msgbus.increment_req_count();
1092 msgbus.increment_res_count();
1093 msgbus.increment_pub_count();
1094 msgbus.dispose();
1095
1096 assert_eq!(msgbus.sent_count(), 0);
1097 assert_eq!(msgbus.req_count(), 0);
1098 assert_eq!(msgbus.res_count(), 0);
1099 assert_eq!(msgbus.pub_count(), 0);
1100 }
1101
1102 #[rstest]
1103 fn test_endpoints_when_no_endpoints() {
1104 let msgbus = get_message_bus();
1105 assert!(msgbus.borrow().endpoints().is_empty());
1106 }
1107
1108 #[rstest]
1109 fn test_topics_when_no_subscriptions() {
1110 let msgbus = get_message_bus();
1111 assert!(msgbus.borrow().patterns().is_empty());
1112 assert!(!msgbus.borrow().has_subscribers("my-topic").unwrap());
1113 }
1114
1115 #[rstest]
1116 fn test_is_subscribed_when_no_subscriptions() {
1117 let msgbus = get_message_bus();
1118 let handler = get_stub_shareable_handler(None);
1119
1120 assert!(!msgbus.borrow().is_subscribed("my-topic", handler));
1121 }
1122
1123 #[rstest]
1124 fn test_get_response_handler_when_no_handler() {
1125 let msgbus = get_message_bus();
1126 let msgbus_ref = msgbus.borrow();
1127 let handler = msgbus_ref.get_response_handler(&UUID4::new());
1128 assert!(handler.is_none());
1129 }
1130
1131 #[rstest]
1132 fn test_get_response_handler_when_already_registered() {
1133 let msgbus = get_message_bus();
1134 let mut msgbus_ref = msgbus.borrow_mut();
1135 let handler = get_stub_shareable_handler(None);
1136
1137 let request_id = UUID4::new();
1138 msgbus_ref
1139 .register_response_handler(&request_id, handler.clone())
1140 .unwrap();
1141
1142 let result = msgbus_ref.register_response_handler(&request_id, handler);
1143 assert!(result.is_err());
1144 }
1145
1146 #[rstest]
1147 fn test_get_response_handler_when_registered() {
1148 let msgbus = get_message_bus();
1149 let mut msgbus_ref = msgbus.borrow_mut();
1150 let handler = get_stub_shareable_handler(None);
1151
1152 let request_id = UUID4::new();
1153 msgbus_ref
1154 .register_response_handler(&request_id, handler)
1155 .unwrap();
1156
1157 let handler = msgbus_ref.get_response_handler(&request_id).unwrap();
1158 assert_eq!(handler.id(), handler.id());
1159 }
1160
1161 #[rstest]
1162 fn test_take_response_handler_removes_registration_and_allows_reregistration() {
1163 let mut msgbus = MessageBus::default();
1164 let request_id = UUID4::new();
1165 let handler = get_stub_shareable_handler(None);
1166 let handler_id = handler.id();
1167 msgbus
1168 .register_response_handler(&request_id, handler)
1169 .unwrap();
1170
1171 let taken = msgbus.take_response_handler(&request_id).unwrap();
1172
1173 assert_eq!(taken.id(), handler_id);
1174 assert!(msgbus.get_response_handler(&request_id).is_none());
1175 assert!(msgbus.register_response_handler(&request_id, taken).is_ok());
1176 }
1177
1178 #[rstest]
1179 fn test_is_registered_when_no_registrations() {
1180 let msgbus = get_message_bus();
1181 assert!(!msgbus.borrow().is_registered("MyEndpoint"));
1182 }
1183
1184 #[rstest]
1185 fn test_register_endpoint() {
1186 let msgbus = get_message_bus();
1187 let endpoint = "MyEndpoint".into();
1188 let handler = get_stub_shareable_handler(None);
1189
1190 msgbus::register_any(endpoint, handler);
1191
1192 assert_eq!(msgbus.borrow().endpoints(), vec![endpoint.to_string()]);
1193 assert!(msgbus.borrow().get_endpoint(endpoint).is_some());
1194 }
1195
1196 #[rstest]
1197 fn test_endpoint_send() {
1198 let msgbus = get_message_bus();
1199 let endpoint = "MyEndpoint".into();
1200 let (handler, checker) = get_call_check_handler(None);
1201 let sent_count = msgbus.borrow().sent_count();
1202
1203 msgbus::register_any(endpoint, handler);
1204 assert!(msgbus.borrow().get_endpoint(endpoint).is_some());
1205 assert!(!checker.was_called());
1206
1207 msgbus::send_any(endpoint, &"Test Message");
1208
1209 assert!(checker.was_called());
1210 assert_eq!(msgbus.borrow().sent_count(), sent_count + 1);
1211 }
1212
1213 #[rstest]
1214 fn test_endpoint_send_value_increments_sent_count() {
1215 let msgbus = get_message_bus();
1216 let endpoint = "MyValueEndpoint".into();
1217 let (handler, checker) = get_call_check_handler(None);
1218 let sent_count = msgbus.borrow().sent_count();
1219
1220 msgbus::register_any(endpoint, handler);
1221 msgbus::send_any_value(endpoint, &"Test Message");
1222
1223 assert!(checker.was_called());
1224 assert_eq!(msgbus.borrow().sent_count(), sent_count + 1);
1225 }
1226
1227 #[rstest]
1228 fn test_publish_any_increments_publish_count() {
1229 let msgbus = get_message_bus();
1230 let topic = "my-published-topic";
1231 let (handler, checker) = get_call_check_handler(None);
1232 let pub_count = msgbus.borrow().pub_count();
1233
1234 msgbus::subscribe_any(topic.into(), handler, None);
1235 msgbus::publish_any(topic.into(), &"Test Message");
1236
1237 assert!(checker.was_called());
1238 assert_eq!(msgbus.borrow().pub_count(), pub_count + 1);
1239 }
1240
1241 #[rstest]
1242 fn test_deregsiter_endpoint() {
1243 let msgbus = get_message_bus();
1244 let endpoint = "MyEndpoint".into();
1245 let handler = get_stub_shareable_handler(None);
1246
1247 msgbus::register_any(endpoint, handler);
1248 msgbus::deregister_any(endpoint);
1249
1250 assert!(msgbus.borrow().endpoints().is_empty());
1251 }
1252
1253 #[rstest]
1254 fn test_subscribe() {
1255 let msgbus = get_message_bus();
1256 let topic = "my-topic";
1257 let handler = get_stub_shareable_handler(None);
1258
1259 msgbus::subscribe_any(topic.into(), handler, Some(1));
1260
1261 assert!(msgbus.borrow().has_subscribers(topic).unwrap());
1262 assert_eq!(msgbus.borrow().patterns(), vec![topic]);
1263 }
1264
1265 #[rstest]
1266 fn test_unsubscribe() {
1267 let msgbus = get_message_bus();
1268 let topic = "my-topic";
1269 let handler = get_stub_shareable_handler(None);
1270
1271 msgbus::subscribe_any(topic.into(), handler.clone(), None);
1272 msgbus::unsubscribe_any(topic.into(), &handler);
1273
1274 assert!(!msgbus.borrow().has_subscribers(topic).unwrap());
1275 assert!(msgbus.borrow().patterns().is_empty());
1276 }
1277
1278 #[rstest]
1279 fn test_subscriptions_count_rejects_invalid_topic() {
1280 let msgbus = get_message_bus();
1281
1282 let err = msgbus
1283 .borrow()
1284 .subscriptions_count("data.*")
1285 .expect_err("wildcards are invalid in topics");
1286
1287 assert_eq!(
1288 err.to_string(),
1289 "Topic `value` contained invalid characters, was data.*"
1290 );
1291 }
1292
1293 #[rstest]
1294 fn test_has_subscribers_rejects_invalid_topic() {
1295 let msgbus = get_message_bus();
1296
1297 let err = msgbus
1298 .borrow()
1299 .has_subscribers("data.*")
1300 .expect_err("wildcards are invalid in topics");
1301
1302 assert_eq!(
1303 err.to_string(),
1304 "Topic `value` contained invalid characters, was data.*"
1305 );
1306 }
1307
1308 #[rstest]
1309 fn test_subscriptions_count_any_rejects_invalid_topic() {
1310 let err = subscriptions_count_any("data.*").expect_err("wildcards are invalid in topics");
1311
1312 assert_eq!(
1313 err.to_string(),
1314 "Topic `value` contained invalid characters, was data.*"
1315 );
1316 }
1317
1318 #[rstest]
1319 fn test_matching_subscriptions() {
1320 let msgbus = get_message_bus();
1321 let pattern = "my-pattern";
1322
1323 let handler_id1 = Ustr::from("1");
1324 let handler1 = get_stub_shareable_handler(Some(handler_id1));
1325
1326 let handler_id2 = Ustr::from("2");
1327 let handler2 = get_stub_shareable_handler(Some(handler_id2));
1328
1329 let handler_id3 = Ustr::from("3");
1330 let handler3 = get_stub_shareable_handler(Some(handler_id3));
1331
1332 let handler_id4 = Ustr::from("4");
1333 let handler4 = get_stub_shareable_handler(Some(handler_id4));
1334
1335 msgbus::subscribe_any(pattern.into(), handler1, None);
1336 msgbus::subscribe_any(pattern.into(), handler2, None);
1337 msgbus::subscribe_any(pattern.into(), handler3, Some(1));
1338 msgbus::subscribe_any(pattern.into(), handler4, Some(2));
1339
1340 assert_eq!(
1341 msgbus.borrow().patterns(),
1342 vec![pattern, pattern, pattern, pattern]
1343 );
1344 assert_eq!(subscriptions_count_any(pattern).unwrap(), 4);
1345
1346 let topic = pattern;
1347 let subs = msgbus.borrow_mut().matching_subscriptions(topic);
1348 assert_eq!(subs.len(), 4);
1349 assert_eq!(subs[0].handler_id, handler_id4);
1350 assert_eq!(subs[1].handler_id, handler_id3);
1351 assert_eq!(subs[2].handler_id, handler_id1);
1352 assert_eq!(subs[3].handler_id, handler_id2);
1353 }
1354
1355 #[rstest]
1356 fn test_matching_subscriptions_orders_by_full_delivery_key_on_cache_miss() {
1357 MessageBus::default().register_message_bus();
1358 let order = Rc::new(RefCell::new(Vec::new()));
1359
1360 msgbus::subscribe_any(
1361 "delivery.*".into(),
1362 recording_any_handler("handler-z", "low-z", order.clone()),
1363 Some(1),
1364 );
1365 msgbus::subscribe_any(
1366 "delivery.topic".into(),
1367 recording_any_handler("handler-b", "exact-b", order.clone()),
1368 Some(10),
1369 );
1370 msgbus::subscribe_any(
1371 "delivery.topic".into(),
1372 recording_any_handler("handler-a", "exact-a", order.clone()),
1373 Some(10),
1374 );
1375 msgbus::subscribe_any(
1376 "delivery.*".into(),
1377 recording_any_handler("handler-b", "wildcard-b", order),
1378 Some(10),
1379 );
1380
1381 let subscriptions = get_message_bus()
1382 .borrow_mut()
1383 .matching_subscriptions("delivery.topic");
1384 let actual = subscriptions
1385 .iter()
1386 .map(|sub| (sub.priority, sub.pattern.as_str(), sub.handler_id.as_str()))
1387 .collect::<Vec<_>>();
1388
1389 assert_eq!(
1390 actual,
1391 vec![
1392 (10, "delivery.*", "handler-b"),
1393 (10, "delivery.topic", "handler-a"),
1394 (10, "delivery.topic", "handler-b"),
1395 (1, "delivery.*", "handler-z"),
1396 ]
1397 );
1398 }
1399
1400 #[rstest]
1401 fn test_first_uncached_publish_any_orders_by_full_delivery_key() {
1402 MessageBus::default().register_message_bus();
1403 let order = Rc::new(RefCell::new(Vec::new()));
1404
1405 msgbus::subscribe_any(
1406 "delivery.*".into(),
1407 recording_any_handler("handler-z", "low-z", order.clone()),
1408 Some(1),
1409 );
1410 msgbus::subscribe_any(
1411 "delivery.topic".into(),
1412 recording_any_handler("handler-b", "exact-b", order.clone()),
1413 Some(10),
1414 );
1415 msgbus::subscribe_any(
1416 "delivery.topic".into(),
1417 recording_any_handler("handler-a", "exact-a", order.clone()),
1418 Some(10),
1419 );
1420 msgbus::subscribe_any(
1421 "delivery.*".into(),
1422 recording_any_handler("handler-b", "wildcard-b", order.clone()),
1423 Some(10),
1424 );
1425
1426 msgbus::publish_any("delivery.topic".into(), &());
1427
1428 assert_eq!(
1429 *order.borrow(),
1430 vec!["wildcard-b", "exact-a", "exact-b", "low-z"]
1431 );
1432 }
1433
1434 #[rstest]
1435 fn test_late_subscription_orders_cached_any_topic_by_full_delivery_key() {
1436 MessageBus::default().register_message_bus();
1437 let order = Rc::new(RefCell::new(Vec::new()));
1438
1439 msgbus::subscribe_any(
1440 "delivery.*".into(),
1441 recording_any_handler("handler-z", "low-z", order.clone()),
1442 Some(1),
1443 );
1444 msgbus::subscribe_any(
1445 "delivery.topic".into(),
1446 recording_any_handler("handler-b", "exact-b", order.clone()),
1447 Some(10),
1448 );
1449 msgbus::subscribe_any(
1450 "delivery.topic".into(),
1451 recording_any_handler("handler-a", "exact-a", order.clone()),
1452 Some(10),
1453 );
1454 msgbus::publish_any("delivery.topic".into(), &());
1455 order.borrow_mut().clear();
1456
1457 msgbus::subscribe_any(
1458 "delivery.*".into(),
1459 recording_any_handler("handler-b", "wildcard-b", order.clone()),
1460 Some(10),
1461 );
1462 msgbus::publish_any("delivery.topic".into(), &());
1463
1464 assert_eq!(
1465 *order.borrow(),
1466 vec!["wildcard-b", "exact-a", "exact-b", "low-z"]
1467 );
1468 }
1469
1470 #[rstest]
1471 fn test_subscription_pattern_matching() {
1472 let msgbus = get_message_bus();
1473 let handler1 = get_stub_shareable_handler(Some(Ustr::from("1")));
1474 let handler2 = get_stub_shareable_handler(Some(Ustr::from("2")));
1475 let handler3 = get_stub_shareable_handler(Some(Ustr::from("3")));
1476
1477 msgbus::subscribe_any("data.quotes.*".into(), handler1, None);
1478 msgbus::subscribe_any("data.trades.*".into(), handler2, None);
1479 msgbus::subscribe_any("data.*.BINANCE.*".into(), handler3, None);
1480 assert_eq!(msgbus.borrow().subscriptions().len(), 3);
1481
1482 let topic = "data.quotes.BINANCE.ETHUSDT";
1483 assert_eq!(msgbus.borrow().find_topic_matches(topic.into()).len(), 2);
1484
1485 let matches = msgbus.borrow_mut().matching_subscriptions(topic);
1486 assert_eq!(matches.len(), 2);
1487 assert_eq!(matches[0].handler_id, Ustr::from("3"));
1488 assert_eq!(matches[1].handler_id, Ustr::from("1"));
1489 }
1490
1491 #[rstest]
1492 fn test_late_wildcard_subscription_receives_cached_topic() {
1493 let msgbus = get_message_bus();
1494 let topic = "data.instrument.POLYMARKET.TEST-SYMBOL";
1495
1496 let (early_handler, early_saver) =
1497 get_any_saving_handler::<String>(Some(Ustr::from("early")));
1498 msgbus::subscribe_any("data.*.POLYMARKET.*".into(), early_handler, None);
1499
1500 msgbus::publish_any(topic.into(), &"ONE".to_string());
1501
1502 let (late_handler, late_saver) = get_any_saving_handler::<String>(Some(Ustr::from("late")));
1503 msgbus::subscribe_any("data.instrument.POLYMARKET.*".into(), late_handler, None);
1504
1505 msgbus::publish_any(topic.into(), &"TWO".to_string());
1506
1507 assert_eq!(early_saver.get_messages(), vec!["ONE", "TWO"]);
1508 assert_eq!(late_saver.get_messages(), vec!["TWO"]);
1509
1510 let topic_mstr: MStr<Topic> = topic.into();
1511 let cached = msgbus.borrow_mut().matching_subscriptions(topic_mstr);
1512 assert_eq!(cached.len(), 2);
1513 }
1514
1515 #[rstest]
1516 fn test_late_wildcard_backfills_into_multiple_cached_topics() {
1517 let msgbus = get_message_bus();
1518 let topics = ["data.A", "data.B", "data.C"];
1519
1520 let (early_handler, early_saver) =
1521 get_any_saving_handler::<String>(Some(Ustr::from("early")));
1522 msgbus::subscribe_any("data.*".into(), early_handler, None);
1523
1524 for topic in &topics {
1525 msgbus::publish_any((*topic).into(), &(*topic).to_string());
1526 }
1527
1528 let (late_handler, late_saver) = get_any_saving_handler::<String>(Some(Ustr::from("late")));
1529 msgbus::subscribe_any("data.*".into(), late_handler, None);
1530
1531 for topic in &topics {
1532 msgbus::publish_any((*topic).into(), &format!("{topic}-2"));
1533 }
1534
1535 assert_eq!(
1536 early_saver.get_messages(),
1537 vec![
1538 "data.A", "data.B", "data.C", "data.A-2", "data.B-2", "data.C-2"
1539 ],
1540 );
1541 assert_eq!(
1542 late_saver.get_messages(),
1543 vec!["data.A-2", "data.B-2", "data.C-2"]
1544 );
1545
1546 for topic in &topics {
1547 let topic_mstr: MStr<Topic> = (*topic).into();
1548 assert_eq!(
1549 msgbus.borrow_mut().matching_subscriptions(topic_mstr).len(),
1550 2,
1551 "topic {topic} should have both subscribers cached",
1552 );
1553 }
1554 }
1555
1556 struct SimpleSubscriptionModel {
1558 subscriptions: Vec<(String, String)>,
1560 }
1561
1562 impl SimpleSubscriptionModel {
1563 fn new() -> Self {
1564 Self {
1565 subscriptions: Vec::new(),
1566 }
1567 }
1568
1569 fn subscribe(&mut self, pattern: &str, handler_id: &str) {
1570 let subscription = (pattern.to_string(), handler_id.to_string());
1571 if !self.subscriptions.contains(&subscription) {
1572 self.subscriptions.push(subscription);
1573 }
1574 }
1575
1576 fn unsubscribe(&mut self, pattern: &str, handler_id: &str) -> bool {
1577 let subscription = (pattern.to_string(), handler_id.to_string());
1578 if let Some(idx) = self.subscriptions.iter().position(|s| s == &subscription) {
1579 self.subscriptions.remove(idx);
1580 true
1581 } else {
1582 false
1583 }
1584 }
1585
1586 fn is_subscribed(&self, pattern: &str, handler_id: &str) -> bool {
1587 self.subscriptions
1588 .contains(&(pattern.to_string(), handler_id.to_string()))
1589 }
1590
1591 fn matching_subscriptions(&self, topic: &str) -> Vec<(String, String)> {
1592 let topic = topic.into();
1593
1594 self.subscriptions
1595 .iter()
1596 .filter(|(pat, _)| is_matching_backtracking(topic, pat.into()))
1597 .map(|(pat, id)| (pat.clone(), id.clone()))
1598 .collect()
1599 }
1600
1601 fn subscription_count(&self) -> usize {
1602 self.subscriptions.len()
1603 }
1604 }
1605
1606 #[rstest]
1607 fn subscription_model_fuzz_testing() {
1608 let mut rng = StdRng::seed_from_u64(42);
1609
1610 let msgbus = get_message_bus();
1611 let mut model = SimpleSubscriptionModel::new();
1612
1613 let mut handlers: Vec<(String, ShareableMessageHandler)> = Vec::new();
1615
1616 let patterns = generate_test_patterns(&mut rng);
1618
1619 let handler_ids: Vec<String> = (0..50).map(|i| format!("handler_{i}")).collect();
1621
1622 for id in &handler_ids {
1624 let handler = get_stub_shareable_handler(Some(Ustr::from(id)));
1625 handlers.push((id.clone(), handler));
1626 }
1627
1628 let num_operations = 50_000;
1629 for op_num in 0..num_operations {
1630 let operation = rng.random_range(0..4);
1631
1632 match operation {
1633 0 => {
1635 let pattern_idx = rng.random_range(0..patterns.len());
1636 let handler_idx = rng.random_range(0..handlers.len());
1637 let pattern = &patterns[pattern_idx];
1638 let (handler_id, handler) = &handlers[handler_idx];
1639
1640 model.subscribe(pattern, handler_id);
1642
1643 msgbus::subscribe_any(pattern.as_str().into(), handler.clone(), None);
1645
1646 assert_eq!(
1647 model.subscription_count(),
1648 msgbus.borrow().subscriptions().len()
1649 );
1650
1651 assert!(
1652 msgbus.borrow().is_subscribed(pattern, handler.clone()),
1653 "Op {op_num}: is_subscribed should return true after subscribe"
1654 );
1655 }
1656
1657 1 => {
1659 if model.subscription_count() > 0 {
1660 let sub_idx = rng.random_range(0..model.subscription_count());
1661 let (pattern, handler_id) = model.subscriptions[sub_idx].clone();
1662
1663 model.unsubscribe(&pattern, &handler_id);
1665
1666 let handler = handlers
1668 .iter()
1669 .find(|(id, _)| id == &handler_id)
1670 .map(|(_, h)| h.clone())
1671 .unwrap();
1672
1673 msgbus::unsubscribe_any(pattern.as_str().into(), &handler);
1675
1676 assert_eq!(
1677 model.subscription_count(),
1678 msgbus.borrow().subscriptions().len()
1679 );
1680 assert!(
1681 !msgbus.borrow().is_subscribed(pattern, handler.clone()),
1682 "Op {op_num}: is_subscribed should return false after unsubscribe"
1683 );
1684 }
1685 }
1686
1687 2 => {
1689 let pattern_idx = rng.random_range(0..patterns.len());
1691 let handler_idx = rng.random_range(0..handlers.len());
1692 let pattern = &patterns[pattern_idx];
1693 let (handler_id, handler) = &handlers[handler_idx];
1694
1695 let expected = model.is_subscribed(pattern, handler_id);
1696 let actual = msgbus.borrow().is_subscribed(pattern, handler.clone());
1697
1698 assert_eq!(
1699 expected, actual,
1700 "Op {op_num}: Subscription state mismatch for pattern '{pattern}', handler '{handler_id}': expected={expected}, actual={actual}"
1701 );
1702 }
1703
1704 3 => {
1706 let topic = create_topic(&mut rng);
1708
1709 let actual_matches = msgbus.borrow_mut().matching_subscriptions(topic);
1710 let expected_matches = model.matching_subscriptions(&topic);
1711
1712 assert_eq!(
1713 expected_matches.len(),
1714 actual_matches.len(),
1715 "Op {}: Match count mismatch for topic '{}': expected={}, actual={}",
1716 op_num,
1717 topic,
1718 expected_matches.len(),
1719 actual_matches.len()
1720 );
1721
1722 for sub in &actual_matches {
1723 assert!(
1724 expected_matches
1725 .contains(&(sub.pattern.to_string(), sub.handler_id.to_string())),
1726 "Op {}: Expected match not found: pattern='{}', handler_id='{}'",
1727 op_num,
1728 sub.pattern,
1729 sub.handler_id
1730 );
1731 }
1732 }
1733 _ => unreachable!(),
1734 }
1735 }
1736 }
1737
1738 fn generate_pattern_from_topic(topic: &str, rng: &mut StdRng) -> String {
1739 let mut pattern = String::new();
1740
1741 for c in topic.chars() {
1742 let val: f64 = rng.random();
1743 if val < 0.1 {
1744 pattern.push('*');
1745 } else if val < 0.3 {
1746 pattern.push('?');
1747 } else if val >= 0.5 {
1748 pattern.push(c);
1749 }
1750 }
1751
1752 pattern
1753 }
1754
1755 fn generate_test_patterns(rng: &mut StdRng) -> Vec<String> {
1756 let mut patterns = vec![
1757 "data.*.*.*".to_string(),
1758 "*.*.BINANCE.*".to_string(),
1759 "events.order.*".to_string(),
1760 "data.*.*.?USDT".to_string(),
1761 "*.trades.*.BTC*".to_string(),
1762 "*.*.*.*".to_string(),
1763 ];
1764
1765 for _ in 0..50 {
1767 match rng.random_range(0..10) {
1768 0..=1 => {
1770 let idx = rng.random_range(0..patterns.len());
1771 patterns.push(patterns[idx].clone());
1772 }
1773 _ => {
1775 let topic = create_topic(rng);
1776 let pattern = generate_pattern_from_topic(&topic, rng);
1777 patterns.push(pattern);
1778 }
1779 }
1780 }
1781
1782 patterns
1783 }
1784
1785 fn create_topic(rng: &mut StdRng) -> Ustr {
1786 let cat = ["data", "info", "order"];
1787 let model = ["quotes", "trades", "orderbooks", "depths"];
1788 let venue = ["BINANCE", "BYBIT", "OKX", "FTX", "KRAKEN"];
1789 let instrument = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "XRPUSDT", "DOGEUSDT"];
1790
1791 let cat = cat[rng.random_range(0..cat.len())];
1792 let model = model[rng.random_range(0..model.len())];
1793 let venue = venue[rng.random_range(0..venue.len())];
1794 let instrument = instrument[rng.random_range(0..instrument.len())];
1795 Ustr::from(&format!("{cat}.{model}.{venue}.{instrument}"))
1796 }
1797}