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, 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/// 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, 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>, // 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 an error 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    /// This function does not return an error.
727    #[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    /// Returns the handler for the `endpoint`.
742    #[must_use]
743    pub fn get_endpoint(&self, endpoint: MStr<Endpoint>) -> Option<&ShareableMessageHandler> {
744        self.endpoints.get(&endpoint)
745    }
746
747    /// Returns the handler for the `correlation_id`.
748    #[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    /// Removes and returns the handler for the `correlation_id`.
757    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    /// Finds the subscriptions with pattern matching the `topic`.
765    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    /// Finds the subscriptions which match the `topic` and caches the
779    /// results in the `patterns` map.
780    #[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    /// Fills a buffer with handlers matching a topic.
795    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    /// Registers a response handler for a specific correlation ID.
817    ///
818    /// # Errors
819    ///
820    /// Returns an error if `handler` is already registered for the `correlation_id`.
821    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    /// A simple reference model for subscription behavior.
1563    struct SimpleSubscriptionModel {
1564        /// Stores (pattern, `handler_id`) tuples for active subscriptions.
1565        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        // Map from handler_id to handler
1620        let mut handlers: Vec<(String, ShareableMessageHandler)> = Vec::new();
1621
1622        // Generate some patterns
1623        let patterns = generate_test_patterns(&mut rng);
1624
1625        // Generate some handler IDs
1626        let handler_ids: Vec<String> = (0..50).map(|i| format!("handler_{i}")).collect();
1627
1628        // Initialize handlers
1629        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                // Subscribe
1640                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                    // Apply to reference model
1647                    model.subscribe(pattern, handler_id);
1648
1649                    // Apply to message bus
1650                    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                // Unsubscribe
1664                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                        // Apply to reference model
1670                        model.unsubscribe(&pattern, &handler_id);
1671
1672                        // Find handler
1673                        let handler = handlers
1674                            .iter()
1675                            .find(|(id, _)| id == &handler_id)
1676                            .map(|(_, h)| h.clone())
1677                            .unwrap();
1678
1679                        // Apply to message bus
1680                        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                // Check is_subscribed
1694                2 => {
1695                    // Get a random pattern and handler
1696                    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                // Check matching_subscriptions
1711                3 => {
1712                    // Generate a topic
1713                    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        // Add some random patterns
1772        for _ in 0..50 {
1773            match rng.random_range(0..10) {
1774                // Use existing pattern
1775                0..=1 => {
1776                    let idx = rng.random_range(0..patterns.len());
1777                    patterns.push(patterns[idx].clone());
1778                }
1779                // Generate new pattern from topic
1780                _ => {
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}