Skip to main content

nautilus_common/msgbus/
core.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Core message bus implementation.
17//!
18//! # Design decisions
19//!
20//! ## Why two routing mechanisms?
21//!
22//! The message bus provides typed and Any-based routing to balance performance
23//! and flexibility:
24//!
25//! **Typed routing** optimizes for throughput on known data types:
26//! - `TopicRouter<T>` for pub/sub, `EndpointMap<T>` for point-to-point.
27//! - Handlers implement `Handler<T>`, receive `&T` directly.
28//! - No runtime type checking enables inlining and static dispatch.
29//! - Built-in routers: `QuoteTick`, `TradeTick`, `Bar`, `OrderBookDeltas`,
30//!   `OrderBookDepth`, `OrderEventAny`, `PositionEvent`, `AccountState`.
31//!
32//! **Any-based routing** provides flexibility for extensibility:
33//! - `subscriptions`/`topics` maps with `ShareableMessageHandler`.
34//! - Handlers implement `Handler<dyn Any>`, receive `&dyn Any`.
35//! - Supports arbitrary message types without modifying the bus.
36//! - Required for Python interop where types aren't known at compile time.
37//!
38//! ## Handler semantics
39//!
40//! **Typed handlers receive `&T` references:**
41//! - Same message delivered to N handlers without cloning.
42//! - Handler decides whether to clone (only if storing).
43//! - Zero-cost for `Copy` types (`QuoteTick`, `TradeTick`, `Bar`).
44//! - Efficient for large types (`OrderBookDeltas`).
45//!
46//! **Any-based handlers pay per-handler overhead:**
47//! - Each handler receives `&dyn Any`, must downcast to `&T`.
48//! - N handlers = N downcasts + N potential clones.
49//! - Runtime type checking on every dispatch.
50//!
51//! ## Performance trade-off
52//!
53//! Typed routing is faster (see `benches/msgbus_typed.rs`, AMD Ryzen 9 7950X):
54//!
55//! | Scenario                    | Typed vs Any |
56//! |-----------------------------|--------------|
57//! | Handler dispatch (noop)     | ~10x faster  |
58//! | Router with 5 subscribers   | ~3.5x faster |
59//! | Router with 10 subscribers  | ~2x faster   |
60//! | High volume (1M messages)   | ~7% faster   |
61//!
62//! Any-based routing pays for flexibility with runtime type checking. Use
63//! typed routing for hot-path data; Any-based for custom types and Python.
64//!
65//! ## Routing paths are separate
66//!
67//! Typed and Any-based routing use separate data structures:
68//! - `publish_quote` routes through `router_quotes`.
69//! - `publish_any` routes through `topics`.
70//!
71//! Publishers and subscribers must use matching APIs. Mixing them causes
72//! silent message loss.
73//!
74//! ## When to use each
75//!
76//! **Typed** (`publish_quote`, `subscribe_quotes`, etc.):
77//! - Market data (quotes, trades, bars, order book updates).
78//! - Order and position events.
79//! - High-frequency data with known types.
80//!
81//! **Any-based** (`publish_any`, `subscribe_any`):
82//! - Custom or user-defined data types.
83//! - Low-frequency messages.
84//! - Python callbacks.
85
86use 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/// Represents a subscription to a particular topic.
135///
136/// This is an internal class intended to be used by the message bus to organize
137/// topics and their subscribers.
138#[derive(Clone, Debug)]
139pub struct Subscription {
140    /// The shareable message handler for the subscription.
141    pub handler: ShareableMessageHandler,
142    /// Store a copy of the handler ID for faster equality checks.
143    pub handler_id: Ustr,
144    /// The pattern for the subscription.
145    pub pattern: MStr<Pattern>,
146    /// The priority for the subscription determines the ordering of handlers receiving
147    /// messages being processed, higher priority handlers will receive messages before
148    /// lower priority handlers.
149    pub priority: u32,
150}
151
152impl Subscription {
153    /// Creates a new [`Subscription`] instance.
154    #[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
206/// A generic message bus to facilitate various messaging patterns.
207///
208/// The bus provides both a producer and consumer API for Pub/Sub, Req/Rep, as
209/// well as direct point-to-point messaging to registered endpoints.
210///
211/// Pub/Sub wildcard patterns for hierarchical topics are possible:
212///  - `*` asterisk represents one or more characters in a pattern.
213///  - `?` question mark represents a single character in a pattern.
214///
215/// Given a topic and pattern potentially containing wildcard characters, i.e.
216/// `*` and `?`, where `?` can match any single character in the topic, and `*`
217/// can match any number of characters including zero characters.
218///
219/// The asterisk in a wildcard matches any character zero or more times. For
220/// example, `comp*` matches anything beginning with `comp` which means `comp`,
221/// `complete`, and `computer` are all matched.
222///
223/// A question mark matches a single character once. For example, `c?mp` matches
224/// `camp` and `comp`. The question mark can also be used more than once.
225/// For example, `c??p` would match both of the above examples and `coop`.
226pub struct MessageBus {
227    /// The trader ID associated with the message bus.
228    pub trader_id: TraderId,
229    /// The instance ID associated with the message bus.
230    pub instance_id: UUID4,
231    /// The name for the message bus.
232    pub name: String,
233    /// If the message bus is backed by a database.
234    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>, // nautilus-import-ok
261    #[cfg(feature = "defi")]
262    pub(crate) router_defi_pools: TopicRouter<nautilus_model::defi::Pool>, // nautilus-import-ok
263    #[cfg(feature = "defi")]
264    pub(crate) router_defi_swaps: TopicRouter<nautilus_model::defi::PoolSwap>, // nautilus-import-ok
265    #[cfg(feature = "defi")]
266    pub(crate) router_defi_liquidity: TopicRouter<nautilus_model::defi::PoolLiquidityUpdate>, // nautilus-import-ok
267    #[cfg(feature = "defi")]
268    pub(crate) router_defi_collects: TopicRouter<nautilus_model::defi::PoolFeeCollect>, // nautilus-import-ok
269    #[cfg(feature = "defi")]
270    pub(crate) router_defi_flash: TopicRouter<nautilus_model::defi::PoolFlash>, // nautilus-import-ok
271    #[cfg(feature = "defi")]
272    pub(crate) endpoints_defi_data: IntoEndpointMap<nautilus_model::defi::DefiData>, // nautilus-import-ok
273    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    /// Creates a new default [`MessageBus`] instance.
312    fn default() -> Self {
313        Self::new(TraderId::from("TRADER-001"), UUID4::new(), None, None)
314    }
315}
316
317impl MessageBus {
318    /// Creates a new [`MessageBus`] instance.
319    #[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    /// Registers message bus for the current thread.
396    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    /// Gets or creates a typed router for custom message type `T`.
403    ///
404    /// # Panics
405    ///
406    /// Panics if the stored router type doesn't match `T` (internal bug).
407    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    /// Gets or creates a typed endpoint map for custom message type `T`.
416    ///
417    /// # Panics
418    ///
419    /// Panics if the stored endpoint map type doesn't match `T` (internal bug).
420    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    /// Sets external egress for serialized published messages.
429    ///
430    /// Typed message variants remain local because this method does not configure external streams.
431    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    /// Sets external egress and category encoding policy from a validated config.
446    ///
447    /// Typed message variants are enabled only when `config.external_streams` is non-empty.
448    ///
449    /// # Errors
450    ///
451    /// Returns a [`crate::config::ConfigError`] if the config selects an unsupported encoding.
452    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    /// Sets the payload type names excluded from external publishing.
474    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    /// Registers a payload type for external-to-internal streaming.
490    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    /// Returns whether the payload type is registered for external-to-internal streaming.
497    #[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    /// Clears all payload types registered for external-to-internal streaming.
503    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    /// Disposes of the message bus, clearing all subscriptions, endpoints,
533    /// and handler references.
534    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    /// Returns the memory address of this instance as a hexadecimal string.
598    #[must_use]
599    pub fn mem_address(&self) -> String {
600        format!("{self:p}")
601    }
602
603    /// Returns a reference to the switchboard.
604    #[must_use]
605    pub fn switchboard(&self) -> &MessagingSwitchboard {
606        &self.switchboard
607    }
608
609    /// Returns the total count of messages sent to endpoints.
610    #[must_use]
611    pub const fn sent_count(&self) -> u64 {
612        self.sent_count
613    }
614
615    /// Returns the total count of requests sent to endpoints.
616    #[must_use]
617    pub const fn req_count(&self) -> u64 {
618        self.req_count
619    }
620
621    /// Returns the total count of responses sent to registered handlers.
622    #[must_use]
623    pub const fn res_count(&self) -> u64 {
624        self.res_count
625    }
626
627    /// Returns the total count of messages published to topics.
628    #[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    /// Returns the registered endpoint addresses.
650    #[must_use]
651    pub fn endpoints(&self) -> Vec<&str> {
652        self.endpoints.iter().map(|e| e.0.as_str()).collect()
653    }
654
655    /// Returns actively subscribed patterns.
656    #[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    /// Returns whether there are subscribers for the `topic`.
665    ///
666    /// # Errors
667    ///
668    /// Returns an error if the `topic` is not a valid topic string.
669    pub fn has_subscribers<T: AsRef<str>>(&self, topic: T) -> anyhow::Result<bool> {
670        Ok(self.subscriptions_count(topic)? > 0)
671    }
672
673    /// Returns the count of subscribers for the `topic`.
674    ///
675    /// # Errors
676    ///
677    /// Returns an error if the `topic` is not a valid topic string.
678    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    /// Returns active subscriptions.
687    #[must_use]
688    pub fn subscriptions(&self) -> Vec<&Subscription> {
689        self.subscriptions.iter().collect()
690    }
691
692    /// Returns the handler IDs for actively subscribed patterns.
693    #[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    /// Returns whether the endpoint is registered.
702    ///
703    /// # Panics
704    ///
705    /// Panics if the `endpoint` conversion to `MStr<Endpoint>` fails.
706    #[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    /// Returns whether the `handler` is subscribed to the `pattern`.
713    #[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    /// Close the message bus which will close the sender channel and join the thread.
725    ///
726    /// # Errors
727    ///
728    /// This function never returns an error (TBD once backing database added).
729    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    /// Returns the handler for the `endpoint`.
740    #[must_use]
741    pub fn get_endpoint(&self, endpoint: MStr<Endpoint>) -> Option<&ShareableMessageHandler> {
742        self.endpoints.get(&endpoint)
743    }
744
745    /// Returns the handler for the `correlation_id`.
746    #[must_use]
747    pub fn get_response_handler(&self, correlation_id: &UUID4) -> Option<&ShareableMessageHandler> {
748        self.correlation_index.get(correlation_id)
749    }
750
751    /// Removes and returns the handler for the `correlation_id`.
752    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    /// Finds the subscriptions with pattern matching the `topic`.
760    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    /// Finds the subscriptions which match the `topic` and caches the
774    /// results in the `patterns` map.
775    #[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    /// Fills a buffer with handlers matching a topic.
790    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    /// Registers a response handler for a specific correlation ID.
812    ///
813    /// # Errors
814    ///
815    /// Returns an error if `handler` is already registered for the `correlation_id`.
816    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    /// A simple reference model for subscription behavior.
1557    struct SimpleSubscriptionModel {
1558        /// Stores (pattern, `handler_id`) tuples for active subscriptions.
1559        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        // Map from handler_id to handler
1614        let mut handlers: Vec<(String, ShareableMessageHandler)> = Vec::new();
1615
1616        // Generate some patterns
1617        let patterns = generate_test_patterns(&mut rng);
1618
1619        // Generate some handler IDs
1620        let handler_ids: Vec<String> = (0..50).map(|i| format!("handler_{i}")).collect();
1621
1622        // Initialize handlers
1623        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                // Subscribe
1634                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                    // Apply to reference model
1641                    model.subscribe(pattern, handler_id);
1642
1643                    // Apply to message bus
1644                    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                // Unsubscribe
1658                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                        // Apply to reference model
1664                        model.unsubscribe(&pattern, &handler_id);
1665
1666                        // Find handler
1667                        let handler = handlers
1668                            .iter()
1669                            .find(|(id, _)| id == &handler_id)
1670                            .map(|(_, h)| h.clone())
1671                            .unwrap();
1672
1673                        // Apply to message bus
1674                        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                // Check is_subscribed
1688                2 => {
1689                    // Get a random pattern and handler
1690                    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                // Check matching_subscriptions
1705                3 => {
1706                    // Generate a topic
1707                    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        // Add some random patterns
1766        for _ in 0..50 {
1767            match rng.random_range(0..10) {
1768                // Use existing pattern
1769                0..=1 => {
1770                    let idx = rng.random_range(0..patterns.len());
1771                    patterns.push(patterns[idx].clone());
1772                }
1773                // Generate new pattern from topic
1774                _ => {
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}