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