Skip to main content

nautilus_common/msgbus/
api.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//! Public API functions for interacting with the message bus.
17//!
18//! This module provides free-standing functions that wrap the thread-local
19//! message bus, providing a convenient API for:
20//!
21//! - Registering endpoint handlers (point-to-point messaging).
22//! - Subscribing to topics (pub/sub messaging).
23//! - Publishing messages to subscribers.
24//! - Sending messages to endpoints.
25
26use std::{any::Any, cell::RefCell, thread::LocalKey};
27
28use nautilus_core::UUID4;
29#[cfg(feature = "defi")]
30use nautilus_model::defi::{
31    Block, DefiData, Pool, PoolFeeCollect, PoolFlash, PoolLiquidityUpdate, PoolSwap,
32};
33use nautilus_model::{
34    data::{
35        Bar, CustomData, Data, FundingRateUpdate, GreeksData, IndexPriceUpdate, MarkPriceUpdate,
36        OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick,
37        option_chain::{OptionChainSlice, OptionGreeks},
38    },
39    events::{AccountState, OrderEventAny, PortfolioSnapshot, PositionEvent},
40    instruments::InstrumentAny,
41    orderbook::OrderBook,
42    orders::OrderAny,
43    position::Position,
44};
45use smallvec::SmallVec;
46use ustr::Ustr;
47
48pub use super::external::republish_external_message;
49use super::{
50    ACCOUNT_STATE_HANDLERS, ANY_HANDLERS, BAR_HANDLERS, BOOK_HANDLERS, BusPayloadType,
51    DELTAS_HANDLERS, DEPTH10_HANDLERS, FUNDING_RATE_HANDLERS, GREEKS_HANDLERS, HANDLER_BUFFER_CAP,
52    INDEX_PRICE_HANDLERS, INSTRUMENT_HANDLERS, MARK_PRICE_HANDLERS, OPTION_CHAIN_HANDLERS,
53    OPTION_GREEKS_HANDLERS, ORDER_EVENT_HANDLERS, PORTFOLIO_SNAPSHOT_HANDLERS,
54    POSITION_EVENT_HANDLERS, QUOTE_HANDLERS, TRADE_HANDLERS,
55    core::{MessageBus, Subscription},
56    dispatch_tap_publish, dispatch_tap_response, dispatch_tap_send,
57    external::forward_to_external_egress,
58    get_message_bus,
59    matching::is_matching_backtracking,
60    mstr::{Endpoint, MStr, Pattern, Topic},
61    try_get_message_bus,
62    typed_handler::{ShareableMessageHandler, TypedHandler, TypedIntoHandler},
63};
64#[cfg(feature = "defi")]
65use super::{
66    DEFI_BLOCK_HANDLERS, DEFI_COLLECT_HANDLERS, DEFI_FLASH_HANDLERS, DEFI_LIQUIDITY_HANDLERS,
67    DEFI_POOL_HANDLERS, DEFI_SWAP_HANDLERS,
68};
69use crate::messages::{
70    data::{DataCommand, DataResponse},
71    execution::{ExecutionReport, TradingCommand},
72};
73
74/// Registers a handler for an endpoint using runtime type dispatch (Any).
75pub fn register_any(endpoint: MStr<Endpoint>, handler: ShareableMessageHandler) {
76    log::debug!(
77        "Registering endpoint '{endpoint}' with handler ID {}",
78        handler.0.id(),
79    );
80    get_message_bus()
81        .borrow_mut()
82        .endpoints
83        .insert(endpoint, handler);
84}
85
86/// Registers a response handler for a correlation ID.
87pub fn register_response_handler(correlation_id: &UUID4, handler: ShareableMessageHandler) {
88    if let Err(e) = get_message_bus()
89        .borrow_mut()
90        .register_response_handler(correlation_id, handler)
91    {
92        log::error!("Failed to register request handler: {e}");
93    }
94}
95
96/// Registers a quote tick handler at an endpoint.
97pub fn register_quote_endpoint(endpoint: MStr<Endpoint>, handler: TypedHandler<QuoteTick>) {
98    get_message_bus()
99        .borrow_mut()
100        .endpoints_quotes
101        .register(endpoint, handler);
102}
103
104/// Returns whether a quote tick handler is registered for the given endpoint.
105#[must_use]
106pub fn has_quote_endpoint(endpoint: MStr<Endpoint>) -> bool {
107    get_message_bus()
108        .borrow()
109        .endpoints_quotes
110        .is_registered(endpoint)
111}
112
113/// Registers a trade tick handler at an endpoint.
114pub fn register_trade_endpoint(endpoint: MStr<Endpoint>, handler: TypedHandler<TradeTick>) {
115    get_message_bus()
116        .borrow_mut()
117        .endpoints_trades
118        .register(endpoint, handler);
119}
120
121/// Registers a bar handler at an endpoint.
122pub fn register_bar_endpoint(endpoint: MStr<Endpoint>, handler: TypedHandler<Bar>) {
123    get_message_bus()
124        .borrow_mut()
125        .endpoints_bars
126        .register(endpoint, handler);
127}
128
129/// Registers an order event handler at an endpoint (ownership-based).
130pub fn register_order_event_endpoint(
131    endpoint: MStr<Endpoint>,
132    handler: TypedIntoHandler<OrderEventAny>,
133) {
134    get_message_bus()
135        .borrow_mut()
136        .endpoints_order_events
137        .register(endpoint, handler);
138}
139
140/// Registers an account state handler at an endpoint.
141pub fn register_account_state_endpoint(
142    endpoint: MStr<Endpoint>,
143    handler: TypedHandler<AccountState>,
144) {
145    get_message_bus()
146        .borrow_mut()
147        .endpoints_account_state
148        .register(endpoint, handler);
149}
150
151/// Registers a trading command handler at an endpoint (ownership-based).
152pub fn register_trading_command_endpoint(
153    endpoint: MStr<Endpoint>,
154    handler: TypedIntoHandler<TradingCommand>,
155) {
156    get_message_bus()
157        .borrow_mut()
158        .endpoints_trading_commands
159        .register(endpoint, handler);
160}
161
162/// Registers a data command handler at an endpoint (ownership-based).
163pub fn register_data_command_endpoint(
164    endpoint: MStr<Endpoint>,
165    handler: TypedIntoHandler<DataCommand>,
166) {
167    get_message_bus()
168        .borrow_mut()
169        .endpoints_data_commands
170        .register(endpoint, handler);
171}
172
173/// Registers a data response handler at an endpoint (ownership-based).
174pub fn register_data_response_endpoint(
175    endpoint: MStr<Endpoint>,
176    handler: TypedIntoHandler<DataResponse>,
177) {
178    get_message_bus()
179        .borrow_mut()
180        .endpoints_data_responses
181        .register(endpoint, handler);
182}
183
184/// Registers an execution report handler at an endpoint (ownership-based).
185pub fn register_execution_report_endpoint(
186    endpoint: MStr<Endpoint>,
187    handler: TypedIntoHandler<ExecutionReport>,
188) {
189    get_message_bus()
190        .borrow_mut()
191        .endpoints_exec_reports
192        .register(endpoint, handler);
193}
194
195/// Registers a data handler at an endpoint (ownership-based).
196pub fn register_data_endpoint(endpoint: MStr<Endpoint>, handler: TypedIntoHandler<Data>) {
197    get_message_bus()
198        .borrow_mut()
199        .endpoints_data
200        .register(endpoint, handler);
201}
202
203/// Registers a DeFi data handler at an endpoint (ownership-based).
204#[cfg(feature = "defi")]
205pub fn register_defi_data_endpoint(endpoint: MStr<Endpoint>, handler: TypedIntoHandler<DefiData>) {
206    get_message_bus()
207        .borrow_mut()
208        .endpoints_defi_data
209        .register(endpoint, handler);
210}
211
212/// Deregisters the handler for an endpoint (Any-based).
213pub fn deregister_any(endpoint: MStr<Endpoint>) {
214    log::debug!("Deregistering endpoint '{endpoint}'");
215    get_message_bus()
216        .borrow_mut()
217        .endpoints
218        .shift_remove(&endpoint);
219}
220
221/// Returns whether an endpoint handler is registered for the given endpoint name.
222///
223/// An invalid endpoint name returns `false`, because registration rejects such names.
224#[must_use]
225pub fn has_endpoint(endpoint: &str) -> bool {
226    let Ok(key) = MStr::<Endpoint>::endpoint(endpoint) else {
227        return false;
228    };
229
230    get_message_bus().borrow().get_endpoint(key).is_some()
231}
232
233/// Subscribes a handler to a pattern using runtime type dispatch (Any).
234///
235/// # Warnings
236///
237/// Assigning priority handling is an advanced feature which *shouldn't
238/// normally be needed by most users*. **Only assign a higher priority to the
239/// subscription if you are certain of what you're doing**. If an inappropriate
240/// priority is assigned then the handler may receive messages before core
241/// system components have been able to process necessary calculations and
242/// produce potential side effects for logically sound behavior.
243pub fn subscribe_any(
244    pattern: MStr<Pattern>,
245    handler: ShareableMessageHandler,
246    priority: Option<u32>,
247) {
248    let msgbus = get_message_bus();
249    let mut msgbus_ref_mut = msgbus.borrow_mut();
250    let sub = Subscription::new(pattern, handler, priority);
251
252    log::debug!(
253        "Subscribing {:?} for pattern '{}'",
254        sub.handler,
255        sub.pattern
256    );
257
258    if msgbus_ref_mut.subscriptions.contains(&sub) {
259        log::warn!("{sub:?} already exists");
260        return;
261    }
262
263    for (topic, subs) in &mut msgbus_ref_mut.topics {
264        if is_matching_backtracking(*topic, sub.pattern) {
265            subs.push(sub.clone());
266            subs.sort_by(Subscription::delivery_order);
267            log::debug!("Added subscription for '{topic}'");
268        }
269    }
270
271    msgbus_ref_mut.subscriptions.insert(sub);
272}
273
274/// Subscribes a handler to instrument messages matching a pattern.
275pub fn subscribe_instruments(
276    pattern: MStr<Pattern>,
277    handler: TypedHandler<InstrumentAny>,
278    priority: Option<u32>,
279) {
280    get_message_bus().borrow_mut().router_instruments.subscribe(
281        pattern,
282        handler,
283        priority.unwrap_or(0),
284    );
285}
286
287/// Subscribes a handler to instrument close messages matching a pattern.
288pub fn subscribe_instrument_close(
289    pattern: MStr<Pattern>,
290    handler: ShareableMessageHandler,
291    priority: Option<u32>,
292) {
293    subscribe_any(pattern, handler, priority);
294}
295
296/// Subscribes a handler to order book deltas matching a pattern.
297pub fn subscribe_book_deltas(
298    pattern: MStr<Pattern>,
299    handler: TypedHandler<OrderBookDeltas>,
300    priority: Option<u32>,
301) {
302    get_message_bus()
303        .borrow_mut()
304        .router_deltas
305        .subscribe(pattern, handler, priority.unwrap_or(0));
306}
307
308/// Subscribes a handler to order book depth10 snapshots matching a pattern.
309pub fn subscribe_book_depth10(
310    pattern: MStr<Pattern>,
311    handler: TypedHandler<OrderBookDepth10>,
312    priority: Option<u32>,
313) {
314    get_message_bus().borrow_mut().router_depth10.subscribe(
315        pattern,
316        handler,
317        priority.unwrap_or(0),
318    );
319}
320
321/// Subscribes a handler to order book snapshots matching a pattern.
322pub fn subscribe_book_snapshots(
323    pattern: MStr<Pattern>,
324    handler: TypedHandler<OrderBook>,
325    priority: Option<u32>,
326) {
327    get_message_bus()
328        .borrow_mut()
329        .router_book_snapshots
330        .subscribe(pattern, handler, priority.unwrap_or(0));
331}
332
333/// Subscribes a handler to quote ticks matching a pattern.
334pub fn subscribe_quotes(
335    pattern: MStr<Pattern>,
336    handler: TypedHandler<QuoteTick>,
337    priority: Option<u32>,
338) {
339    get_message_bus()
340        .borrow_mut()
341        .router_quotes
342        .subscribe(pattern, handler, priority.unwrap_or(0));
343}
344
345/// Subscribes a handler to trade ticks matching a pattern.
346pub fn subscribe_trades(
347    pattern: MStr<Pattern>,
348    handler: TypedHandler<TradeTick>,
349    priority: Option<u32>,
350) {
351    get_message_bus()
352        .borrow_mut()
353        .router_trades
354        .subscribe(pattern, handler, priority.unwrap_or(0));
355}
356
357/// Subscribes a handler to bars matching a pattern.
358pub fn subscribe_bars(pattern: MStr<Pattern>, handler: TypedHandler<Bar>, priority: Option<u32>) {
359    get_message_bus()
360        .borrow_mut()
361        .router_bars
362        .subscribe(pattern, handler, priority.unwrap_or(0));
363}
364
365/// Subscribes a handler to mark price updates matching a pattern.
366pub fn subscribe_mark_prices(
367    pattern: MStr<Pattern>,
368    handler: TypedHandler<MarkPriceUpdate>,
369    priority: Option<u32>,
370) {
371    get_message_bus().borrow_mut().router_mark_prices.subscribe(
372        pattern,
373        handler,
374        priority.unwrap_or(0),
375    );
376}
377
378/// Subscribes a handler to index price updates matching a pattern.
379pub fn subscribe_index_prices(
380    pattern: MStr<Pattern>,
381    handler: TypedHandler<IndexPriceUpdate>,
382    priority: Option<u32>,
383) {
384    get_message_bus()
385        .borrow_mut()
386        .router_index_prices
387        .subscribe(pattern, handler, priority.unwrap_or(0));
388}
389
390/// Subscribes a handler to funding rate updates matching a pattern.
391pub fn subscribe_funding_rates(
392    pattern: MStr<Pattern>,
393    handler: TypedHandler<FundingRateUpdate>,
394    priority: Option<u32>,
395) {
396    get_message_bus()
397        .borrow_mut()
398        .router_funding_rates
399        .subscribe(pattern, handler, priority.unwrap_or(0));
400}
401
402/// Subscribes a handler to greeks data matching a pattern.
403pub fn subscribe_greeks(
404    pattern: MStr<Pattern>,
405    handler: TypedHandler<GreeksData>,
406    priority: Option<u32>,
407) {
408    get_message_bus()
409        .borrow_mut()
410        .router_greeks
411        .subscribe(pattern, handler, priority.unwrap_or(0));
412}
413
414/// Subscribes a handler to option greeks updates matching a pattern.
415pub fn subscribe_option_greeks(
416    pattern: MStr<Pattern>,
417    handler: TypedHandler<OptionGreeks>,
418    priority: Option<u32>,
419) {
420    get_message_bus()
421        .borrow_mut()
422        .router_option_greeks
423        .subscribe(pattern, handler, priority.unwrap_or(0));
424}
425
426/// Subscribes a handler to option chain slice updates matching a pattern.
427pub fn subscribe_option_chain(
428    pattern: MStr<Pattern>,
429    handler: TypedHandler<OptionChainSlice>,
430    priority: Option<u32>,
431) {
432    get_message_bus()
433        .borrow_mut()
434        .router_option_chain
435        .subscribe(pattern, handler, priority.unwrap_or(0));
436}
437
438/// Subscribes a handler to order events matching a pattern.
439pub fn subscribe_order_events(
440    pattern: MStr<Pattern>,
441    handler: TypedHandler<OrderEventAny>,
442    priority: Option<u32>,
443) {
444    get_message_bus()
445        .borrow_mut()
446        .router_order_events
447        .subscribe(pattern, handler, priority.unwrap_or(0));
448}
449
450/// Subscribes a handler to position events matching a pattern.
451pub fn subscribe_position_events(
452    pattern: MStr<Pattern>,
453    handler: TypedHandler<PositionEvent>,
454    priority: Option<u32>,
455) {
456    get_message_bus()
457        .borrow_mut()
458        .router_position_events
459        .subscribe(pattern, handler, priority.unwrap_or(0));
460}
461
462/// Subscribes a handler to positions matching a pattern.
463pub fn subscribe_positions(
464    pattern: MStr<Pattern>,
465    handler: TypedHandler<Position>,
466    priority: Option<u32>,
467) {
468    get_message_bus().borrow_mut().router_positions.subscribe(
469        pattern,
470        handler,
471        priority.unwrap_or(0),
472    );
473}
474
475/// Subscribes a handler to account state updates matching a pattern.
476pub fn subscribe_account_state(
477    pattern: MStr<Pattern>,
478    handler: TypedHandler<AccountState>,
479    priority: Option<u32>,
480) {
481    get_message_bus()
482        .borrow_mut()
483        .router_account_state
484        .subscribe(pattern, handler, priority.unwrap_or(0));
485}
486
487/// Subscribes a handler to portfolio snapshots matching a pattern.
488pub fn subscribe_portfolio_snapshot(
489    pattern: MStr<Pattern>,
490    handler: TypedHandler<PortfolioSnapshot>,
491    priority: Option<u32>,
492) {
493    get_message_bus().borrow_mut().router_portfolio.subscribe(
494        pattern,
495        handler,
496        priority.unwrap_or(0),
497    );
498}
499
500/// Subscribes a handler to DeFi blocks matching a pattern.
501#[cfg(feature = "defi")]
502pub fn subscribe_defi_blocks(
503    pattern: MStr<Pattern>,
504    handler: TypedHandler<Block>,
505    priority: Option<u32>,
506) {
507    get_message_bus().borrow_mut().router_defi_blocks.subscribe(
508        pattern,
509        handler,
510        priority.unwrap_or(0),
511    );
512}
513
514/// Subscribes a handler to DeFi pools matching a pattern.
515#[cfg(feature = "defi")]
516pub fn subscribe_defi_pools(
517    pattern: MStr<Pattern>,
518    handler: TypedHandler<Pool>,
519    priority: Option<u32>,
520) {
521    get_message_bus().borrow_mut().router_defi_pools.subscribe(
522        pattern,
523        handler,
524        priority.unwrap_or(0),
525    );
526}
527
528/// Subscribes a handler to DeFi pool swaps matching a pattern.
529#[cfg(feature = "defi")]
530pub fn subscribe_defi_swaps(
531    pattern: MStr<Pattern>,
532    handler: TypedHandler<PoolSwap>,
533    priority: Option<u32>,
534) {
535    get_message_bus().borrow_mut().router_defi_swaps.subscribe(
536        pattern,
537        handler,
538        priority.unwrap_or(0),
539    );
540}
541
542/// Subscribes a handler to DeFi liquidity updates matching a pattern.
543#[cfg(feature = "defi")]
544pub fn subscribe_defi_liquidity(
545    pattern: MStr<Pattern>,
546    handler: TypedHandler<PoolLiquidityUpdate>,
547    priority: Option<u32>,
548) {
549    get_message_bus()
550        .borrow_mut()
551        .router_defi_liquidity
552        .subscribe(pattern, handler, priority.unwrap_or(0));
553}
554
555/// Subscribes a handler to DeFi fee collects matching a pattern.
556#[cfg(feature = "defi")]
557pub fn subscribe_defi_collects(
558    pattern: MStr<Pattern>,
559    handler: TypedHandler<PoolFeeCollect>,
560    priority: Option<u32>,
561) {
562    get_message_bus()
563        .borrow_mut()
564        .router_defi_collects
565        .subscribe(pattern, handler, priority.unwrap_or(0));
566}
567
568/// Subscribes a handler to DeFi flash loans matching a pattern.
569#[cfg(feature = "defi")]
570pub fn subscribe_defi_flash(
571    pattern: MStr<Pattern>,
572    handler: TypedHandler<PoolFlash>,
573    priority: Option<u32>,
574) {
575    get_message_bus().borrow_mut().router_defi_flash.subscribe(
576        pattern,
577        handler,
578        priority.unwrap_or(0),
579    );
580}
581
582/// Unsubscribes a handler from instrument messages.
583pub fn unsubscribe_instruments(pattern: MStr<Pattern>, handler: &TypedHandler<InstrumentAny>) {
584    get_message_bus()
585        .borrow_mut()
586        .router_instruments
587        .unsubscribe(pattern, handler);
588}
589
590/// Unsubscribes a handler from instrument close messages.
591pub fn unsubscribe_instrument_close(pattern: MStr<Pattern>, handler: &ShareableMessageHandler) {
592    unsubscribe_any(pattern, handler);
593}
594
595/// Unsubscribes a handler from order book deltas.
596pub fn unsubscribe_book_deltas(pattern: MStr<Pattern>, handler: &TypedHandler<OrderBookDeltas>) {
597    get_message_bus()
598        .borrow_mut()
599        .router_deltas
600        .unsubscribe(pattern, handler);
601}
602
603/// Unsubscribes a handler from order book depth10 snapshots.
604pub fn unsubscribe_book_depth10(pattern: MStr<Pattern>, handler: &TypedHandler<OrderBookDepth10>) {
605    get_message_bus()
606        .borrow_mut()
607        .router_depth10
608        .unsubscribe(pattern, handler);
609}
610
611/// Unsubscribes a handler from order book snapshots.
612pub fn unsubscribe_book_snapshots(pattern: MStr<Pattern>, handler: &TypedHandler<OrderBook>) {
613    get_message_bus()
614        .borrow_mut()
615        .router_book_snapshots
616        .unsubscribe(pattern, handler);
617}
618
619/// Unsubscribes a handler from quote ticks.
620pub fn unsubscribe_quotes(pattern: MStr<Pattern>, handler: &TypedHandler<QuoteTick>) {
621    get_message_bus()
622        .borrow_mut()
623        .router_quotes
624        .unsubscribe(pattern, handler);
625}
626
627/// Unsubscribes a handler from trade ticks.
628pub fn unsubscribe_trades(pattern: MStr<Pattern>, handler: &TypedHandler<TradeTick>) {
629    get_message_bus()
630        .borrow_mut()
631        .router_trades
632        .unsubscribe(pattern, handler);
633}
634
635/// Unsubscribes a handler from bars.
636pub fn unsubscribe_bars(pattern: MStr<Pattern>, handler: &TypedHandler<Bar>) {
637    get_message_bus()
638        .borrow_mut()
639        .router_bars
640        .unsubscribe(pattern, handler);
641}
642
643/// Unsubscribes a handler from mark price updates.
644pub fn unsubscribe_mark_prices(pattern: MStr<Pattern>, handler: &TypedHandler<MarkPriceUpdate>) {
645    get_message_bus()
646        .borrow_mut()
647        .router_mark_prices
648        .unsubscribe(pattern, handler);
649}
650
651/// Unsubscribes a handler from index price updates.
652pub fn unsubscribe_index_prices(pattern: MStr<Pattern>, handler: &TypedHandler<IndexPriceUpdate>) {
653    get_message_bus()
654        .borrow_mut()
655        .router_index_prices
656        .unsubscribe(pattern, handler);
657}
658
659/// Unsubscribes a handler from funding rate updates.
660pub fn unsubscribe_funding_rates(
661    pattern: MStr<Pattern>,
662    handler: &TypedHandler<FundingRateUpdate>,
663) {
664    get_message_bus()
665        .borrow_mut()
666        .router_funding_rates
667        .unsubscribe(pattern, handler);
668}
669
670/// Unsubscribes a handler from account state updates.
671pub fn unsubscribe_account_state(pattern: MStr<Pattern>, handler: &TypedHandler<AccountState>) {
672    get_message_bus()
673        .borrow_mut()
674        .router_account_state
675        .unsubscribe(pattern, handler);
676}
677
678/// Unsubscribes a handler from portfolio snapshots.
679pub fn unsubscribe_portfolio_snapshot(
680    pattern: MStr<Pattern>,
681    handler: &TypedHandler<PortfolioSnapshot>,
682) {
683    get_message_bus()
684        .borrow_mut()
685        .router_portfolio
686        .unsubscribe(pattern, handler);
687}
688
689/// Unsubscribes a handler from order events.
690pub fn unsubscribe_order_events(pattern: MStr<Pattern>, handler: &TypedHandler<OrderEventAny>) {
691    get_message_bus()
692        .borrow_mut()
693        .router_order_events
694        .unsubscribe(pattern, handler);
695}
696
697/// Unsubscribes a handler from position events.
698pub fn unsubscribe_position_events(pattern: MStr<Pattern>, handler: &TypedHandler<PositionEvent>) {
699    get_message_bus()
700        .borrow_mut()
701        .router_position_events
702        .unsubscribe(pattern, handler);
703}
704
705/// Removes a specific order event handler by pattern and handler ID.
706pub fn remove_order_event_handler(pattern: MStr<Pattern>, handler_id: Ustr) {
707    get_message_bus()
708        .borrow_mut()
709        .router_order_events
710        .remove_handler(pattern, handler_id);
711}
712
713/// Removes a specific position event handler by pattern and handler ID.
714pub fn remove_position_event_handler(pattern: MStr<Pattern>, handler_id: Ustr) {
715    get_message_bus()
716        .borrow_mut()
717        .router_position_events
718        .remove_handler(pattern, handler_id);
719}
720
721/// Unsubscribes a handler from orders.
722pub fn unsubscribe_orders(pattern: MStr<Pattern>, handler: &TypedHandler<OrderAny>) {
723    get_message_bus()
724        .borrow_mut()
725        .router_orders
726        .unsubscribe(pattern, handler);
727}
728
729/// Unsubscribes a handler from positions.
730pub fn unsubscribe_positions(pattern: MStr<Pattern>, handler: &TypedHandler<Position>) {
731    get_message_bus()
732        .borrow_mut()
733        .router_positions
734        .unsubscribe(pattern, handler);
735}
736
737/// Unsubscribes a handler from greeks data.
738pub fn unsubscribe_greeks(pattern: MStr<Pattern>, handler: &TypedHandler<GreeksData>) {
739    get_message_bus()
740        .borrow_mut()
741        .router_greeks
742        .unsubscribe(pattern, handler);
743}
744
745/// Unsubscribes a handler from option greeks updates.
746pub fn unsubscribe_option_greeks(pattern: MStr<Pattern>, handler: &TypedHandler<OptionGreeks>) {
747    get_message_bus()
748        .borrow_mut()
749        .router_option_greeks
750        .unsubscribe(pattern, handler);
751}
752
753/// Unsubscribes a handler from option chain slice updates.
754pub fn unsubscribe_option_chain(pattern: MStr<Pattern>, handler: &TypedHandler<OptionChainSlice>) {
755    get_message_bus()
756        .borrow_mut()
757        .router_option_chain
758        .unsubscribe(pattern, handler);
759}
760
761/// Unsubscribes a handler from DeFi blocks.
762#[cfg(feature = "defi")]
763pub fn unsubscribe_defi_blocks(pattern: MStr<Pattern>, handler: &TypedHandler<Block>) {
764    get_message_bus()
765        .borrow_mut()
766        .router_defi_blocks
767        .unsubscribe(pattern, handler);
768}
769
770/// Unsubscribes a handler from DeFi pools.
771#[cfg(feature = "defi")]
772pub fn unsubscribe_defi_pools(pattern: MStr<Pattern>, handler: &TypedHandler<Pool>) {
773    get_message_bus()
774        .borrow_mut()
775        .router_defi_pools
776        .unsubscribe(pattern, handler);
777}
778
779/// Unsubscribes a handler from DeFi pool swaps.
780#[cfg(feature = "defi")]
781pub fn unsubscribe_defi_swaps(pattern: MStr<Pattern>, handler: &TypedHandler<PoolSwap>) {
782    get_message_bus()
783        .borrow_mut()
784        .router_defi_swaps
785        .unsubscribe(pattern, handler);
786}
787
788/// Unsubscribes a handler from DeFi liquidity updates.
789#[cfg(feature = "defi")]
790pub fn unsubscribe_defi_liquidity(
791    pattern: MStr<Pattern>,
792    handler: &TypedHandler<PoolLiquidityUpdate>,
793) {
794    get_message_bus()
795        .borrow_mut()
796        .router_defi_liquidity
797        .unsubscribe(pattern, handler);
798}
799
800/// Unsubscribes a handler from DeFi fee collects.
801#[cfg(feature = "defi")]
802pub fn unsubscribe_defi_collects(pattern: MStr<Pattern>, handler: &TypedHandler<PoolFeeCollect>) {
803    get_message_bus()
804        .borrow_mut()
805        .router_defi_collects
806        .unsubscribe(pattern, handler);
807}
808
809/// Unsubscribes a handler from DeFi flash loans.
810#[cfg(feature = "defi")]
811pub fn unsubscribe_defi_flash(pattern: MStr<Pattern>, handler: &TypedHandler<PoolFlash>) {
812    get_message_bus()
813        .borrow_mut()
814        .router_defi_flash
815        .unsubscribe(pattern, handler);
816}
817
818/// Unsubscribes a handler from a pattern (Any-based).
819pub fn unsubscribe_any(pattern: MStr<Pattern>, handler: &ShareableMessageHandler) {
820    log::debug!("Unsubscribing {handler:?} from pattern '{pattern}'");
821
822    let handler_id = handler.0.id();
823    let bus_rc = get_message_bus();
824    let mut bus = bus_rc.borrow_mut();
825
826    let count_before = bus.subscriptions.len();
827
828    bus.topics.values_mut().for_each(|subs| {
829        subs.retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
830    });
831
832    bus.subscriptions
833        .retain(|s| !(s.pattern == pattern && s.handler_id == handler_id));
834
835    let removed = bus.subscriptions.len() < count_before;
836
837    if removed {
838        log::debug!("Handler for pattern '{pattern}' was removed");
839    } else {
840        log::debug!("No matching handler for pattern '{pattern}' was found");
841    }
842}
843
844/// Checks if a handler is subscribed to a pattern (Any-based).
845pub fn is_subscribed_any<T: AsRef<str>>(pattern: T, handler: ShareableMessageHandler) -> bool {
846    let pattern = MStr::from(pattern.as_ref());
847    let sub = Subscription::new(pattern, handler, None);
848    get_message_bus().borrow().subscriptions.contains(&sub)
849}
850
851/// Returns the count of Any-based subscriptions for a topic.
852///
853/// # Errors
854///
855/// Returns an error if the `topic` is not a valid topic string.
856pub fn subscriptions_count_any<S: AsRef<str>>(topic: S) -> anyhow::Result<usize> {
857    get_message_bus().borrow().subscriptions_count(topic)
858}
859
860/// Returns the subscriber count for order book deltas on a topic.
861pub fn subscriber_count_deltas(topic: MStr<Topic>) -> usize {
862    get_message_bus()
863        .borrow()
864        .router_deltas
865        .subscriber_count(topic)
866}
867
868/// Returns the subscriber count for order book depth10 on a topic.
869pub fn subscriber_count_depth10(topic: MStr<Topic>) -> usize {
870    get_message_bus()
871        .borrow()
872        .router_depth10
873        .subscriber_count(topic)
874}
875
876/// Returns the subscriber count for order book snapshots on a topic.
877pub fn subscriber_count_book_snapshots(topic: MStr<Topic>) -> usize {
878    get_message_bus()
879        .borrow()
880        .router_book_snapshots
881        .subscriber_count(topic)
882}
883
884/// Returns the exact subscriber count for quotes on a topic,
885/// excluding wildcard pattern subscriptions.
886pub fn exact_subscriber_count_quotes(topic: MStr<Topic>) -> usize {
887    get_message_bus()
888        .borrow()
889        .router_quotes
890        .exact_subscriber_count(topic)
891}
892
893/// Returns the exact subscriber count for trades on a topic,
894/// excluding wildcard pattern subscriptions.
895pub fn exact_subscriber_count_trades(topic: MStr<Topic>) -> usize {
896    get_message_bus()
897        .borrow()
898        .router_trades
899        .exact_subscriber_count(topic)
900}
901
902/// Returns the exact subscriber count for mark prices on a topic,
903/// excluding wildcard pattern subscriptions.
904pub fn exact_subscriber_count_mark_prices(topic: MStr<Topic>) -> usize {
905    get_message_bus()
906        .borrow()
907        .router_mark_prices
908        .exact_subscriber_count(topic)
909}
910
911/// Returns the exact subscriber count for index prices on a topic,
912/// excluding wildcard pattern subscriptions.
913pub fn exact_subscriber_count_index_prices(topic: MStr<Topic>) -> usize {
914    get_message_bus()
915        .borrow()
916        .router_index_prices
917        .exact_subscriber_count(topic)
918}
919
920/// Returns the exact subscriber count for funding rates on a topic,
921/// excluding wildcard pattern subscriptions.
922pub fn exact_subscriber_count_funding_rates(topic: MStr<Topic>) -> usize {
923    get_message_bus()
924        .borrow()
925        .router_funding_rates
926        .exact_subscriber_count(topic)
927}
928
929/// Returns the exact subscriber count for option greeks on a topic,
930/// excluding wildcard pattern subscriptions.
931pub fn exact_subscriber_count_option_greeks(topic: MStr<Topic>) -> usize {
932    get_message_bus()
933        .borrow()
934        .router_option_greeks
935        .exact_subscriber_count(topic)
936}
937
938/// Returns the exact subscriber count for bars on a topic,
939/// excluding wildcard pattern subscriptions.
940pub fn exact_subscriber_count_bars(topic: MStr<Topic>) -> usize {
941    get_message_bus()
942        .borrow()
943        .router_bars
944        .exact_subscriber_count(topic)
945}
946
947/// Publishes a message to the topic using runtime type dispatch (Any).
948pub fn publish_any(topic: MStr<Topic>, message: &dyn Any) {
949    dispatch_tap_publish(topic, message);
950
951    // Take buffer (re-entrancy safe)
952    let mut handlers = ANY_HANDLERS.with_borrow_mut(std::mem::take);
953
954    {
955        let bus_rc = get_message_bus();
956        let mut bus = bus_rc.borrow_mut();
957        bus.fill_matching_any_handlers(topic, &mut handlers);
958        bus.increment_pub_count();
959    }
960
961    for handler in &handlers {
962        handler.0.handle(message);
963    }
964
965    handlers.clear(); // Release refs before restore
966    ANY_HANDLERS.with_borrow_mut(|buf| *buf = handlers);
967
968    let Some(custom) = message.downcast_ref::<CustomData>() else {
969        return;
970    };
971
972    forward_to_external_egress(
973        topic,
974        BusPayloadType::Custom(Ustr::from(custom.data.type_name())),
975        custom,
976    );
977}
978
979/// Tries to publish a message to the current thread's registered message bus.
980///
981/// Returns `false` when the thread has no bus or the bus is already borrowed.
982pub fn try_publish_any(topic: MStr<Topic>, message: &dyn Any) -> bool {
983    let Some(bus_rc) = try_get_message_bus() else {
984        return false;
985    };
986
987    if bus_rc.try_borrow_mut().is_err() {
988        return false;
989    }
990
991    dispatch_tap_publish(topic, message);
992
993    let Ok(mut bus) = bus_rc.try_borrow_mut() else {
994        return false;
995    };
996
997    // Take buffer (re-entrancy safe)
998    let mut handlers = ANY_HANDLERS.with_borrow_mut(std::mem::take);
999
1000    bus.fill_matching_any_handlers(topic, &mut handlers);
1001    bus.increment_pub_count();
1002    drop(bus);
1003
1004    for handler in &handlers {
1005        handler.0.handle(message);
1006    }
1007
1008    handlers.clear(); // Release refs before restore
1009    ANY_HANDLERS.with_borrow_mut(|buf| *buf = handlers);
1010    true
1011}
1012
1013/// Publishes an instrument to subscribers on a topic.
1014pub fn publish_instrument(topic: MStr<Topic>, instrument: &InstrumentAny) {
1015    publish_typed(
1016        topic,
1017        &INSTRUMENT_HANDLERS,
1018        |bus, h| bus.router_instruments.fill_matching_handlers(topic, h),
1019        instrument,
1020    );
1021
1022    forward_to_external_egress(topic, BusPayloadType::Instrument, instrument);
1023}
1024
1025/// Publishes order book deltas to subscribers on a topic.
1026pub fn publish_deltas(topic: MStr<Topic>, deltas: &OrderBookDeltas) {
1027    publish_typed(
1028        topic,
1029        &DELTAS_HANDLERS,
1030        |bus, h| bus.router_deltas.fill_matching_handlers(topic, h),
1031        deltas,
1032    );
1033
1034    forward_to_external_egress(topic, BusPayloadType::OrderBookDeltas, deltas);
1035}
1036
1037/// Publishes order book depth10 to subscribers on a topic.
1038pub fn publish_depth10(topic: MStr<Topic>, depth: &OrderBookDepth10) {
1039    publish_typed(
1040        topic,
1041        &DEPTH10_HANDLERS,
1042        |bus, h| bus.router_depth10.fill_matching_handlers(topic, h),
1043        depth,
1044    );
1045
1046    forward_to_external_egress(topic, BusPayloadType::OrderBookDepth10, depth);
1047}
1048
1049/// Publishes an order book snapshot to subscribers on a topic.
1050pub fn publish_book(topic: MStr<Topic>, book: &OrderBook) {
1051    publish_typed(
1052        topic,
1053        &BOOK_HANDLERS,
1054        |bus, h| bus.router_book_snapshots.fill_matching_handlers(topic, h),
1055        book,
1056    );
1057}
1058
1059/// Publishes a quote tick to subscribers on a topic.
1060pub fn publish_quote(topic: MStr<Topic>, quote: &QuoteTick) {
1061    publish_typed(
1062        topic,
1063        &QUOTE_HANDLERS,
1064        |bus, h| bus.router_quotes.fill_matching_handlers(topic, h),
1065        quote,
1066    );
1067
1068    forward_to_external_egress(topic, BusPayloadType::QuoteTick, quote);
1069}
1070
1071/// Publishes a trade tick to subscribers on a topic.
1072pub fn publish_trade(topic: MStr<Topic>, trade: &TradeTick) {
1073    publish_typed(
1074        topic,
1075        &TRADE_HANDLERS,
1076        |bus, h| bus.router_trades.fill_matching_handlers(topic, h),
1077        trade,
1078    );
1079
1080    forward_to_external_egress(topic, BusPayloadType::TradeTick, trade);
1081}
1082
1083/// Publishes a bar to subscribers on a topic.
1084pub fn publish_bar(topic: MStr<Topic>, bar: &Bar) {
1085    publish_typed(
1086        topic,
1087        &BAR_HANDLERS,
1088        |bus, h| bus.router_bars.fill_matching_handlers(topic, h),
1089        bar,
1090    );
1091
1092    forward_to_external_egress(topic, BusPayloadType::Bar, bar);
1093}
1094
1095/// Publishes a mark price update to subscribers on a topic.
1096pub fn publish_mark_price(topic: MStr<Topic>, mark_price: &MarkPriceUpdate) {
1097    publish_typed(
1098        topic,
1099        &MARK_PRICE_HANDLERS,
1100        |bus, h| bus.router_mark_prices.fill_matching_handlers(topic, h),
1101        mark_price,
1102    );
1103
1104    forward_to_external_egress(topic, BusPayloadType::MarkPriceUpdate, mark_price);
1105}
1106
1107/// Publishes an index price update to subscribers on a topic.
1108pub fn publish_index_price(topic: MStr<Topic>, index_price: &IndexPriceUpdate) {
1109    publish_typed(
1110        topic,
1111        &INDEX_PRICE_HANDLERS,
1112        |bus, h| bus.router_index_prices.fill_matching_handlers(topic, h),
1113        index_price,
1114    );
1115
1116    forward_to_external_egress(topic, BusPayloadType::IndexPriceUpdate, index_price);
1117}
1118
1119/// Publishes a funding rate update to subscribers on a topic.
1120pub fn publish_funding_rate(topic: MStr<Topic>, funding_rate: &FundingRateUpdate) {
1121    publish_typed(
1122        topic,
1123        &FUNDING_RATE_HANDLERS,
1124        |bus, h| bus.router_funding_rates.fill_matching_handlers(topic, h),
1125        funding_rate,
1126    );
1127
1128    forward_to_external_egress(topic, BusPayloadType::FundingRateUpdate, funding_rate);
1129}
1130
1131/// Publishes greeks data to subscribers on a topic.
1132pub fn publish_greeks(topic: MStr<Topic>, greeks: &GreeksData) {
1133    publish_typed(
1134        topic,
1135        &GREEKS_HANDLERS,
1136        |bus, h| bus.router_greeks.fill_matching_handlers(topic, h),
1137        greeks,
1138    );
1139}
1140
1141/// Publishes option greeks to subscribers on a topic.
1142pub fn publish_option_greeks(topic: MStr<Topic>, option_greeks: &OptionGreeks) {
1143    publish_typed(
1144        topic,
1145        &OPTION_GREEKS_HANDLERS,
1146        |bus, h| bus.router_option_greeks.fill_matching_handlers(topic, h),
1147        option_greeks,
1148    );
1149
1150    forward_to_external_egress(topic, BusPayloadType::OptionGreeks, option_greeks);
1151}
1152
1153/// Publishes an option chain slice to subscribers on a topic.
1154pub fn publish_option_chain(topic: MStr<Topic>, slice: &OptionChainSlice) {
1155    publish_typed(
1156        topic,
1157        &OPTION_CHAIN_HANDLERS,
1158        |bus, h| bus.router_option_chain.fill_matching_handlers(topic, h),
1159        slice,
1160    );
1161}
1162
1163/// Publishes an account state to subscribers on a topic.
1164pub fn publish_account_state(topic: MStr<Topic>, state: &AccountState) {
1165    publish_typed(
1166        topic,
1167        &ACCOUNT_STATE_HANDLERS,
1168        |bus, h| bus.router_account_state.fill_matching_handlers(topic, h),
1169        state,
1170    );
1171
1172    forward_to_external_egress(topic, BusPayloadType::AccountState, state);
1173}
1174
1175/// Publishes a portfolio snapshot to subscribers on a topic.
1176pub fn publish_portfolio_snapshot(topic: MStr<Topic>, snapshot: &PortfolioSnapshot) {
1177    publish_typed(
1178        topic,
1179        &PORTFOLIO_SNAPSHOT_HANDLERS,
1180        |bus, h| {
1181            bus.router_portfolio.fill_matching_handlers(topic, h);
1182        },
1183        snapshot,
1184    );
1185
1186    forward_to_external_egress(topic, BusPayloadType::PortfolioSnapshot, snapshot);
1187}
1188
1189/// Publishes an order event to subscribers on a topic.
1190pub fn publish_order_event(topic: MStr<Topic>, event: &OrderEventAny) {
1191    publish_typed(
1192        topic,
1193        &ORDER_EVENT_HANDLERS,
1194        |bus, h| bus.router_order_events.fill_matching_handlers(topic, h),
1195        event,
1196    );
1197
1198    forward_to_external_egress(topic, BusPayloadType::OrderEvent, event);
1199}
1200
1201/// Publishes a position event to subscribers on a topic.
1202pub fn publish_position_event(topic: MStr<Topic>, event: &PositionEvent) {
1203    publish_typed(
1204        topic,
1205        &POSITION_EVENT_HANDLERS,
1206        |bus, h| bus.router_position_events.fill_matching_handlers(topic, h),
1207        event,
1208    );
1209
1210    forward_to_external_egress(topic, BusPayloadType::PositionEvent, event);
1211}
1212
1213/// Publishes a DeFi block to subscribers on a topic.
1214#[cfg(feature = "defi")]
1215pub fn publish_defi_block(topic: MStr<Topic>, block: &Block) {
1216    publish_typed(
1217        topic,
1218        &DEFI_BLOCK_HANDLERS,
1219        |bus, h| bus.router_defi_blocks.fill_matching_handlers(topic, h),
1220        block,
1221    );
1222
1223    forward_to_external_egress(topic, BusPayloadType::Block, block);
1224}
1225
1226/// Publishes a DeFi pool to subscribers on a topic.
1227#[cfg(feature = "defi")]
1228pub fn publish_defi_pool(topic: MStr<Topic>, pool: &Pool) {
1229    publish_typed(
1230        topic,
1231        &DEFI_POOL_HANDLERS,
1232        |bus, h| bus.router_defi_pools.fill_matching_handlers(topic, h),
1233        pool,
1234    );
1235
1236    forward_to_external_egress(topic, BusPayloadType::Pool, pool);
1237}
1238
1239/// Publishes a DeFi pool swap to subscribers on a topic.
1240#[cfg(feature = "defi")]
1241pub fn publish_defi_swap(topic: MStr<Topic>, swap: &PoolSwap) {
1242    publish_typed(
1243        topic,
1244        &DEFI_SWAP_HANDLERS,
1245        |bus, h| bus.router_defi_swaps.fill_matching_handlers(topic, h),
1246        swap,
1247    );
1248}
1249
1250/// Publishes a DeFi liquidity update to subscribers on a topic.
1251#[cfg(feature = "defi")]
1252pub fn publish_defi_liquidity(topic: MStr<Topic>, update: &PoolLiquidityUpdate) {
1253    publish_typed(
1254        topic,
1255        &DEFI_LIQUIDITY_HANDLERS,
1256        |bus, h| bus.router_defi_liquidity.fill_matching_handlers(topic, h),
1257        update,
1258    );
1259
1260    forward_to_external_egress(topic, BusPayloadType::PoolLiquidityUpdate, update);
1261}
1262
1263/// Publishes a DeFi fee collect to subscribers on a topic.
1264#[cfg(feature = "defi")]
1265pub fn publish_defi_collect(topic: MStr<Topic>, collect: &PoolFeeCollect) {
1266    publish_typed(
1267        topic,
1268        &DEFI_COLLECT_HANDLERS,
1269        |bus, h| bus.router_defi_collects.fill_matching_handlers(topic, h),
1270        collect,
1271    );
1272
1273    forward_to_external_egress(topic, BusPayloadType::PoolFeeCollect, collect);
1274}
1275
1276/// Publishes a DeFi flash loan to subscribers on a topic.
1277#[cfg(feature = "defi")]
1278pub fn publish_defi_flash(topic: MStr<Topic>, flash: &PoolFlash) {
1279    publish_typed(
1280        topic,
1281        &DEFI_FLASH_HANDLERS,
1282        |bus, h| bus.router_defi_flash.fill_matching_handlers(topic, h),
1283        flash,
1284    );
1285
1286    forward_to_external_egress(topic, BusPayloadType::PoolFlash, flash);
1287}
1288
1289/// Publishes a message to typed handlers using thread-local buffer reuse.
1290///
1291/// The `fill_fn` receives a mutable reference to the `MessageBus`, avoiding
1292/// redundant TLS access and Rc clone/drop overhead per publish.
1293///
1294/// Before fanout the registered bus tap (if any) observes the message. Capture must
1295/// precede subscriber dispatch so the durable record exists before any handler reacts
1296/// to the message.
1297///
1298/// # Invariants
1299///
1300/// - `fill_fn` must not call any publish path (would panic from `RefCell` double-borrow).
1301/// - Handler panics drop the buffer, losing reuse optimization (acceptable as panics are fatal).
1302#[inline]
1303fn publish_typed<T: 'static>(
1304    topic: MStr<Topic>,
1305    tls: &'static LocalKey<RefCell<SmallVec<[TypedHandler<T>; HANDLER_BUFFER_CAP]>>>,
1306    fill_fn: impl FnOnce(&mut MessageBus, &mut SmallVec<[TypedHandler<T>; HANDLER_BUFFER_CAP]>),
1307    message: &T,
1308) {
1309    dispatch_tap_publish(topic, message);
1310
1311    // Take buffer (re-entrancy safe)
1312    let mut handlers = tls.with_borrow_mut(std::mem::take);
1313
1314    // Borrow scope ends before dispatch to support re-entrant publishes
1315    let bus_rc = get_message_bus();
1316    {
1317        let mut bus = bus_rc.borrow_mut();
1318        fill_fn(&mut bus, &mut handlers);
1319        bus.increment_pub_count();
1320    }
1321
1322    for handler in &handlers {
1323        handler.handle(message);
1324    }
1325
1326    handlers.clear(); // Release refs before restore
1327    tls.with_borrow_mut(|buf| *buf = handlers);
1328}
1329
1330/// Sends a message to an endpoint handler using runtime type dispatch (Any).
1331pub fn send_any(endpoint: MStr<Endpoint>, message: &dyn Any) {
1332    send_any_inner(endpoint, message, "send_any");
1333}
1334
1335/// Sends a message to an endpoint, converting to Any (convenience wrapper).
1336pub fn send_any_value<T: 'static>(endpoint: MStr<Endpoint>, message: &T) {
1337    send_any_inner(endpoint, message, "send_any_value");
1338}
1339
1340#[inline]
1341fn send_any_inner(endpoint: MStr<Endpoint>, message: &dyn Any, fn_name: &str) {
1342    dispatch_tap_send(endpoint, message);
1343
1344    let handler = {
1345        let bus = get_message_bus();
1346        let mut bus = bus.borrow_mut();
1347        let handler = bus.get_endpoint(endpoint).cloned();
1348        if handler.is_some() {
1349            bus.increment_sent_count();
1350        }
1351        handler
1352    };
1353
1354    if let Some(handler) = handler {
1355        handler.0.handle(message);
1356    } else {
1357        log::error!("{fn_name}: no registered endpoint '{endpoint}'");
1358    }
1359}
1360
1361/// Sends the [`DataResponse`] to the registered correlation ID handler.
1362pub fn send_response(correlation_id: &UUID4, message: &DataResponse) {
1363    dispatch_tap_response(correlation_id, message);
1364
1365    let handler = {
1366        let bus = get_message_bus();
1367        let mut bus = bus.borrow_mut();
1368        let handler = bus.take_response_handler(correlation_id);
1369        bus.increment_res_count();
1370        handler
1371    };
1372
1373    if let Some(handler) = handler {
1374        match message {
1375            DataResponse::Data(resp) => handler.0.handle(resp),
1376            DataResponse::Instrument(resp) => handler.0.handle(resp.as_ref()),
1377            DataResponse::Instruments(resp) => handler.0.handle(resp),
1378            DataResponse::Book(resp) => handler.0.handle(resp),
1379            DataResponse::BookDeltas(resp) => handler.0.handle(resp),
1380            DataResponse::BookDepth(resp) => handler.0.handle(resp),
1381            DataResponse::Quotes(resp) => handler.0.handle(resp),
1382            DataResponse::Trades(resp) => handler.0.handle(resp),
1383            DataResponse::FundingRates(resp) => handler.0.handle(resp),
1384            DataResponse::ForwardPrices(resp) => handler.0.handle(resp),
1385            DataResponse::Bars(resp) => handler.0.handle(resp),
1386        }
1387    } else {
1388        log::error!("send_response: handler not found for correlation_id '{correlation_id}'");
1389    }
1390}
1391
1392/// Sends a quote tick to an endpoint handler.
1393pub fn send_quote(endpoint: MStr<Endpoint>, quote: &QuoteTick) {
1394    send_endpoint_ref(
1395        endpoint,
1396        quote,
1397        |bus| bus.endpoints_quotes.get(endpoint),
1398        "send_quote",
1399    );
1400}
1401
1402/// Sends a trade tick to an endpoint handler.
1403pub fn send_trade(endpoint: MStr<Endpoint>, trade: &TradeTick) {
1404    send_endpoint_ref(
1405        endpoint,
1406        trade,
1407        |bus| bus.endpoints_trades.get(endpoint),
1408        "send_trade",
1409    );
1410}
1411
1412/// Sends a bar to an endpoint handler.
1413pub fn send_bar(endpoint: MStr<Endpoint>, bar: &Bar) {
1414    send_endpoint_ref(
1415        endpoint,
1416        bar,
1417        |bus| bus.endpoints_bars.get(endpoint),
1418        "send_bar",
1419    );
1420}
1421
1422/// Sends an order event to an endpoint handler, transferring ownership.
1423pub fn send_order_event(endpoint: MStr<Endpoint>, event: OrderEventAny) {
1424    send_endpoint_owned(
1425        endpoint,
1426        event,
1427        |bus| bus.endpoints_order_events.get(endpoint),
1428        "send_order_event",
1429    );
1430}
1431
1432/// Sends an account state to an endpoint handler.
1433pub fn send_account_state(endpoint: MStr<Endpoint>, state: &AccountState) {
1434    send_endpoint_ref(
1435        endpoint,
1436        state,
1437        |bus| bus.endpoints_account_state.get(endpoint),
1438        "send_account_state",
1439    );
1440}
1441
1442/// Sends a trading command to an endpoint handler, transferring ownership.
1443pub fn send_trading_command(endpoint: MStr<Endpoint>, command: TradingCommand) {
1444    send_endpoint_owned(
1445        endpoint,
1446        command,
1447        |bus| bus.endpoints_trading_commands.get(endpoint),
1448        "send_trading_command",
1449    );
1450}
1451
1452/// Sends a data command to an endpoint handler, transferring ownership.
1453pub fn send_data_command(endpoint: MStr<Endpoint>, command: DataCommand) {
1454    let is_request = data_command_is_request(&command);
1455    send_endpoint_owned_counted(
1456        endpoint,
1457        command,
1458        |bus| bus.endpoints_data_commands.get(endpoint),
1459        "send_data_command",
1460        is_request,
1461    );
1462}
1463
1464/// Sends a data response to an endpoint handler, transferring ownership.
1465pub fn send_data_response(endpoint: MStr<Endpoint>, response: DataResponse) {
1466    send_endpoint_owned(
1467        endpoint,
1468        response,
1469        |bus| bus.endpoints_data_responses.get(endpoint),
1470        "send_data_response",
1471    );
1472}
1473
1474/// Sends an execution report to an endpoint handler, transferring ownership.
1475pub fn send_execution_report(endpoint: MStr<Endpoint>, report: ExecutionReport) {
1476    send_endpoint_owned(
1477        endpoint,
1478        report,
1479        |bus| bus.endpoints_exec_reports.get(endpoint),
1480        "send_execution_report",
1481    );
1482}
1483
1484/// Sends data to an endpoint handler, transferring ownership.
1485pub fn send_data(endpoint: MStr<Endpoint>, data: Data) {
1486    send_endpoint_owned(
1487        endpoint,
1488        data,
1489        |bus| bus.endpoints_data.get(endpoint),
1490        "send_data",
1491    );
1492}
1493
1494/// Sends DeFi data to an endpoint handler, transferring ownership.
1495#[cfg(feature = "defi")]
1496pub fn send_defi_data(endpoint: MStr<Endpoint>, data: DefiData) {
1497    send_endpoint_owned(
1498        endpoint,
1499        data,
1500        |bus| bus.endpoints_defi_data.get(endpoint),
1501        "send_defi_data",
1502    );
1503}
1504
1505#[inline]
1506fn send_endpoint_ref<T: 'static, F>(
1507    endpoint: MStr<Endpoint>,
1508    message: &T,
1509    get_handler: F,
1510    fn_name: &str,
1511) where
1512    F: FnOnce(&MessageBus) -> Option<&TypedHandler<T>>,
1513{
1514    dispatch_tap_send(endpoint, message);
1515
1516    let handler = {
1517        let bus = get_message_bus();
1518        let mut bus = bus.borrow_mut();
1519        let handler = get_handler(&bus).cloned();
1520        if handler.is_some() {
1521            bus.increment_sent_count();
1522        }
1523        handler
1524    };
1525
1526    if let Some(handler) = handler {
1527        handler.handle(message);
1528    } else {
1529        log::error!("{fn_name}: no registered endpoint '{endpoint}'");
1530    }
1531}
1532
1533#[inline]
1534fn send_endpoint_owned<T: 'static, F>(
1535    endpoint: MStr<Endpoint>,
1536    message: T,
1537    get_handler: F,
1538    fn_name: &str,
1539) where
1540    F: FnOnce(&MessageBus) -> Option<&TypedIntoHandler<T>>,
1541{
1542    send_endpoint_owned_counted(endpoint, message, get_handler, fn_name, false);
1543}
1544
1545#[inline]
1546fn send_endpoint_owned_counted<T: 'static, F>(
1547    endpoint: MStr<Endpoint>,
1548    message: T,
1549    get_handler: F,
1550    fn_name: &str,
1551    count_request: bool,
1552) where
1553    F: FnOnce(&MessageBus) -> Option<&TypedIntoHandler<T>>,
1554{
1555    // Capture before the dispatch consumes `message`
1556    dispatch_tap_send(endpoint, &message);
1557
1558    let handler = {
1559        let bus = get_message_bus();
1560        let mut bus = bus.borrow_mut();
1561        let handler = get_handler(&bus).cloned();
1562        if handler.is_some() {
1563            bus.increment_sent_count();
1564            if count_request {
1565                bus.increment_req_count();
1566            }
1567        }
1568        handler
1569    };
1570
1571    if let Some(handler) = handler {
1572        handler.handle(message);
1573    } else {
1574        log::error!("{fn_name}: no registered endpoint '{endpoint}'");
1575    }
1576}
1577
1578#[inline]
1579fn data_command_is_request(command: &DataCommand) -> bool {
1580    match command {
1581        DataCommand::Request(_) => true,
1582        #[cfg(feature = "defi")]
1583        DataCommand::DefiRequest(_) => true,
1584        _ => false,
1585    }
1586}
1587
1588#[cfg(test)]
1589mod tests {
1590    //! Tests for the message bus API functions.
1591    //!
1592    //! Includes re-entrancy tests that verify handlers can call back into the
1593    //! message bus without causing `RefCell` borrow conflicts. This is the scenario
1594    //! where `send_*` holds a borrow, calls the handler, and the handler needs to
1595    //! call `borrow_mut()` for topic getters or other operations.
1596
1597    #[cfg(feature = "defi")]
1598    use std::sync::Arc;
1599    use std::{
1600        cell::{Cell, RefCell},
1601        rc::Rc,
1602        thread,
1603    };
1604
1605    #[cfg(feature = "defi")]
1606    use alloy_primitives::{U256, address};
1607    use bytes::Bytes;
1608    use nautilus_core::{UUID4, UnixNanos};
1609    #[cfg(feature = "defi")]
1610    use nautilus_model::defi::{
1611        AmmType, Chain, Dex, DexType, PoolIdentifier, PoolLiquidityUpdateType, Token,
1612    };
1613    #[cfg(any(feature = "sbe", feature = "capnp"))]
1614    use nautilus_model::{data::OptionGreekValues, enums::GreeksConvention};
1615    use nautilus_model::{
1616        data::{
1617            Bar, BarType, DataType, FundingRateUpdate, IndexPriceUpdate, MarkPriceUpdate,
1618            OptionGreeks, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick,
1619            stubs::{stub_custom_data, stub_deltas, stub_depth10},
1620        },
1621        enums::{AccountType, BookType, OrderSide, PositionSide},
1622        events::{OrderEventAny, PositionEvent, PositionOpened, order::spec::OrderDeniedSpec},
1623        identifiers::{
1624            AccountId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId,
1625            Venue,
1626        },
1627        instruments::{InstrumentAny, stubs::audusd_sim},
1628        orderbook::OrderBook,
1629        types::{Currency, Price, Quantity},
1630    };
1631    #[cfg(feature = "sbe")]
1632    use nautilus_serialization::sbe::FromSbe;
1633    #[cfg(feature = "capnp")]
1634    use nautilus_serialization::{capnp::FromCapnp, market_capnp};
1635    use rstest::rstest;
1636    use rust_decimal::Decimal;
1637
1638    use super::*;
1639    use crate::{
1640        enums::SerializationEncoding,
1641        messages::{
1642            data::{
1643                BarsResponse, BookDeltasResponse, BookDepthResponse, BookResponse,
1644                CustomDataResponse, DataCommand, DataResponse, ForwardPricesResponse,
1645                FundingRatesResponse, InstrumentResponse, InstrumentsResponse, QuotesResponse,
1646                RequestCommand, RequestQuotes, SubscribeCommand, SubscribeQuotes, TradesResponse,
1647            },
1648            execution::{CancelAllOrders, TradingCommand},
1649        },
1650        msgbus::{
1651            BusMessage, BusTap, MessageBusConfig, MessageBusExternalEgress, SuppressExternalGuard,
1652            clear_bus_tap, set_bus_tap, set_message_bus, stubs::get_call_check_handler,
1653        },
1654    };
1655
1656    #[derive(Debug)]
1657    struct CapturedEgressMessage {
1658        topic: String,
1659        encoding: SerializationEncoding,
1660        payload_type: BusPayloadType,
1661        payload: Bytes,
1662    }
1663
1664    struct CapturingExternalEgress {
1665        publications: Rc<RefCell<Vec<CapturedEgressMessage>>>,
1666        closed: Cell<bool>,
1667    }
1668
1669    impl CapturingExternalEgress {
1670        fn new() -> (Self, Rc<RefCell<Vec<CapturedEgressMessage>>>) {
1671            let publications = Rc::new(RefCell::new(Vec::new()));
1672            (
1673                Self {
1674                    publications: publications.clone(),
1675                    closed: Cell::new(false),
1676                },
1677                publications,
1678            )
1679        }
1680    }
1681
1682    impl MessageBusExternalEgress for CapturingExternalEgress {
1683        fn is_closed(&self) -> bool {
1684            self.closed.get()
1685        }
1686
1687        fn publish(&self, message: BusMessage) {
1688            self.publications.borrow_mut().push(CapturedEgressMessage {
1689                topic: message.topic.to_string(),
1690                encoding: message.encoding,
1691                payload_type: message.payload_type,
1692                payload: message.payload,
1693            });
1694        }
1695
1696        fn close(&mut self) {
1697            self.closed.set(true);
1698        }
1699    }
1700
1701    fn install_capturing_external_egress(
1702        encoding: SerializationEncoding,
1703    ) -> Rc<RefCell<Vec<CapturedEgressMessage>>> {
1704        let msgbus = Rc::new(RefCell::new(MessageBus::default()));
1705        set_message_bus(msgbus.clone());
1706        let (external_egress, publications) = CapturingExternalEgress::new();
1707        msgbus
1708            .borrow_mut()
1709            .set_external_egress(Box::new(external_egress), encoding);
1710        publications
1711    }
1712
1713    fn install_capturing_external_egress_config(
1714        config: &MessageBusConfig,
1715    ) -> Rc<RefCell<Vec<CapturedEgressMessage>>> {
1716        let msgbus = Rc::new(RefCell::new(MessageBus::default()));
1717        set_message_bus(msgbus.clone());
1718        let (external_egress, publications) = CapturingExternalEgress::new();
1719        msgbus
1720            .borrow_mut()
1721            .set_external_egress_config(Box::new(external_egress), config)
1722            .expect("message bus config must be valid");
1723        publications
1724    }
1725
1726    fn reset_message_bus() {
1727        get_message_bus().borrow_mut().dispose();
1728        set_message_bus(Rc::new(RefCell::new(MessageBus::default())));
1729    }
1730
1731    fn assert_response_handler_is_consumed<T>(response: &DataResponse)
1732    where
1733        T: 'static,
1734    {
1735        reset_message_bus();
1736
1737        let correlation_id = *response.correlation_id();
1738        let response_kind = response.kind();
1739        let calls = Rc::new(Cell::new(0));
1740        let handler_calls = calls.clone();
1741        register_response_handler(
1742            &correlation_id,
1743            ShareableMessageHandler::from_typed(move |_response: &T| {
1744                handler_calls.set(handler_calls.get() + 1);
1745            }),
1746        );
1747
1748        send_response(&correlation_id, response);
1749        send_response(&correlation_id, response);
1750
1751        assert_eq!(
1752            calls.get(),
1753            1,
1754            "{response_kind} response handler must only run once",
1755        );
1756        assert!(
1757            get_message_bus()
1758                .borrow()
1759                .get_response_handler(&correlation_id)
1760                .is_none(),
1761            "{response_kind} response handler must be removed after dispatch",
1762        );
1763        assert_eq!(
1764            get_message_bus().borrow().res_count(),
1765            2,
1766            "{response_kind} duplicate responses must still increment the response count",
1767        );
1768    }
1769
1770    #[rstest]
1771    #[case("")]
1772    #[case("   ")]
1773    #[case("\t\n")]
1774    #[case("*")]
1775    #[case("mailbox.*")]
1776    #[case("mail?ox")]
1777    fn has_endpoint_returns_false_for_invalid_name(#[case] endpoint: &str) {
1778        reset_message_bus();
1779
1780        assert!(!has_endpoint(endpoint));
1781    }
1782
1783    #[rstest]
1784    #[case(SerializationEncoding::MsgPack)]
1785    #[case(SerializationEncoding::Json)]
1786    fn publish_quote_forwards_decodable_payload_to_external_egress(
1787        #[case] encoding: SerializationEncoding,
1788    ) {
1789        let publications = install_capturing_external_egress(encoding);
1790        let quote = QuoteTick::default();
1791
1792        publish_quote("data.quotes.TEST".into(), &quote);
1793
1794        let publications = publications.borrow();
1795        assert_eq!(publications.len(), 1);
1796        assert_eq!(publications[0].topic, "data.quotes.TEST");
1797
1798        let decoded: QuoteTick = match encoding {
1799            SerializationEncoding::MsgPack => rmp_serde::from_slice(&publications[0].payload)
1800                .expect("MsgPack payload must decode as QuoteTick"),
1801            SerializationEncoding::Json => serde_json::from_slice(&publications[0].payload)
1802                .expect("JSON payload must decode as QuoteTick"),
1803            SerializationEncoding::Sbe | SerializationEncoding::Capnp => {
1804                unreachable!("schema encodings are tested separately")
1805            }
1806        };
1807        let payload_value: serde_json::Value = match encoding {
1808            SerializationEncoding::MsgPack => rmp_serde::from_slice(&publications[0].payload)
1809                .expect("MsgPack payload must decode as a value"),
1810            SerializationEncoding::Json => serde_json::from_slice(&publications[0].payload)
1811                .expect("JSON payload must decode as a value"),
1812            SerializationEncoding::Sbe | SerializationEncoding::Capnp => {
1813                unreachable!("schema encodings are tested separately")
1814            }
1815        };
1816        assert_eq!(
1817            payload_value.get("type").and_then(|value| value.as_str()),
1818            Some("QuoteTick")
1819        );
1820        assert_eq!(decoded, quote);
1821        drop(publications);
1822        reset_message_bus();
1823    }
1824
1825    fn assert_quote_round_trips(encoding: SerializationEncoding) {
1826        let publications = install_capturing_external_egress(encoding);
1827        let quote = QuoteTick::default();
1828
1829        publish_quote("data.quotes.TEST".into(), &quote);
1830
1831        let bus_message = {
1832            let publications = publications.borrow();
1833            assert_eq!(publications.len(), 1);
1834            assert_eq!(publications[0].payload_type, BusPayloadType::QuoteTick);
1835            assert_eq!(publications[0].encoding, encoding);
1836            BusMessage::with_str_topic(
1837                publications[0].topic.clone(),
1838                publications[0].payload_type,
1839                publications[0].payload.clone(),
1840                publications[0].encoding,
1841            )
1842        };
1843        publications.borrow_mut().clear();
1844
1845        let received = Rc::new(RefCell::new(Vec::<QuoteTick>::new()));
1846        let received_handler = received.clone();
1847        let handler = TypedHandler::from(move |quote: &QuoteTick| {
1848            received_handler.borrow_mut().push(*quote);
1849        });
1850        subscribe_quotes("data.quotes.*".into(), handler, None);
1851
1852        get_message_bus()
1853            .borrow_mut()
1854            .add_streaming_type(BusPayloadType::QuoteTick);
1855        republish_external_message(&bus_message).unwrap();
1856
1857        assert_eq!(*received.borrow(), vec![quote]);
1858        assert!(
1859            publications.borrow().is_empty(),
1860            "republished message must not be forwarded back out externally"
1861        );
1862        reset_message_bus();
1863    }
1864
1865    #[rstest]
1866    #[case(SerializationEncoding::Json)]
1867    #[case(SerializationEncoding::MsgPack)]
1868    fn republish_external_message_round_trips_quote(#[case] encoding: SerializationEncoding) {
1869        assert_quote_round_trips(encoding);
1870    }
1871
1872    #[cfg(feature = "sbe")]
1873    #[rstest]
1874    fn republish_external_message_round_trips_quote_sbe() {
1875        assert_quote_round_trips(SerializationEncoding::Sbe);
1876    }
1877
1878    #[cfg(feature = "capnp")]
1879    #[rstest]
1880    fn republish_external_message_round_trips_quote_capnp() {
1881        assert_quote_round_trips(SerializationEncoding::Capnp);
1882    }
1883
1884    fn assert_typed_external_round_trips<T>(
1885        encoding: SerializationEncoding,
1886        payload_type: BusPayloadType,
1887        topic: &str,
1888        value: T,
1889        publish: fn(MStr<Topic>, &T),
1890        subscribe: fn(MStr<Pattern>, TypedHandler<T>, Option<u32>),
1891        assert_received: impl Fn(&T, &T),
1892    ) where
1893        T: Clone + 'static,
1894    {
1895        let publications = install_capturing_external_egress(encoding);
1896
1897        publish(topic.into(), &value);
1898
1899        let bus_message = {
1900            let publications = publications.borrow();
1901            assert_eq!(publications.len(), 1);
1902            assert_eq!(publications[0].payload_type, payload_type);
1903            assert_eq!(publications[0].encoding, encoding);
1904            BusMessage::with_str_topic(
1905                publications[0].topic.clone(),
1906                publications[0].payload_type,
1907                publications[0].payload.clone(),
1908                publications[0].encoding,
1909            )
1910        };
1911        publications.borrow_mut().clear();
1912
1913        let received = Rc::new(RefCell::new(Vec::<T>::new()));
1914        let received_handler = received.clone();
1915        let handler = TypedHandler::from(move |message: &T| {
1916            received_handler.borrow_mut().push(message.clone());
1917        });
1918        subscribe(topic.into(), handler, None);
1919
1920        get_message_bus()
1921            .borrow_mut()
1922            .add_streaming_type(payload_type);
1923        republish_external_message(&bus_message).unwrap();
1924
1925        let received = received.borrow();
1926        assert_eq!(received.len(), 1);
1927        assert_received(&received[0], &value);
1928        assert!(
1929            publications.borrow().is_empty(),
1930            "republished message must not be forwarded back out externally"
1931        );
1932        reset_message_bus();
1933    }
1934
1935    fn assert_eq_ref<T>(actual: &T, expected: &T)
1936    where
1937        T: PartialEq + std::fmt::Debug,
1938    {
1939        assert_eq!(actual, expected);
1940    }
1941
1942    fn assert_json_value_eq<T>(actual: &T, expected: &T)
1943    where
1944        T: serde::Serialize,
1945    {
1946        assert_eq!(
1947            serde_json::to_value(actual).expect("actual value must serialize"),
1948            serde_json::to_value(expected).expect("expected value must serialize"),
1949        );
1950    }
1951
1952    fn assert_depth10_market_eq(actual: &OrderBookDepth10, expected: &OrderBookDepth10) {
1953        assert_eq!(actual.instrument_id, expected.instrument_id);
1954        assert_eq!(actual.bid_counts, expected.bid_counts);
1955        assert_eq!(actual.ask_counts, expected.ask_counts);
1956        assert_eq!(actual.flags, expected.flags);
1957        assert_eq!(actual.sequence, expected.sequence);
1958        assert_eq!(actual.ts_event, expected.ts_event);
1959        assert_eq!(actual.ts_init, expected.ts_init);
1960
1961        for (actual, expected) in actual.bids.iter().zip(expected.bids.iter()) {
1962            assert_eq!(actual.side, expected.side);
1963            assert_eq!(actual.price, expected.price);
1964            assert_eq!(actual.size, expected.size);
1965        }
1966
1967        for (actual, expected) in actual.asks.iter().zip(expected.asks.iter()) {
1968            assert_eq!(actual.side, expected.side);
1969            assert_eq!(actual.price, expected.price);
1970            assert_eq!(actual.size, expected.size);
1971        }
1972    }
1973
1974    fn mark_price_update() -> MarkPriceUpdate {
1975        MarkPriceUpdate::new(
1976            InstrumentId::from("AUDUSD.SIM"),
1977            Price::from("1.00010"),
1978            UnixNanos::from(1),
1979            UnixNanos::from(2),
1980        )
1981    }
1982
1983    fn index_price_update() -> IndexPriceUpdate {
1984        IndexPriceUpdate::new(
1985            InstrumentId::from("AUDUSD.SIM"),
1986            Price::from("1.00020"),
1987            UnixNanos::from(3),
1988            UnixNanos::from(4),
1989        )
1990    }
1991
1992    fn funding_rate_update() -> FundingRateUpdate {
1993        FundingRateUpdate::new(
1994            InstrumentId::from("AUDUSD.SIM"),
1995            Decimal::new(1, 4),
1996            Some(480),
1997            Some(UnixNanos::from(5)),
1998            UnixNanos::from(6),
1999            UnixNanos::from(7),
2000        )
2001    }
2002
2003    #[cfg(any(feature = "sbe", feature = "capnp"))]
2004    fn option_greeks() -> OptionGreeks {
2005        OptionGreeks {
2006            instrument_id: InstrumentId::from("BTC-30JUN23-40000-C.DERIBIT"),
2007            convention: GreeksConvention::PriceAdjusted,
2008            greeks: OptionGreekValues {
2009                delta: 0.525,
2010                gamma: 0.00032,
2011                vega: 12.25,
2012                theta: -0.72,
2013                rho: 0.18,
2014            },
2015            mark_iv: Some(0.0),
2016            bid_iv: None,
2017            ask_iv: Some(0.54),
2018            underlying_price: Some(41_500.25),
2019            open_interest: Some(0.0),
2020            ts_event: UnixNanos::from(20),
2021            ts_init: UnixNanos::from(21),
2022        }
2023    }
2024
2025    fn portfolio_snapshot() -> PortfolioSnapshot {
2026        PortfolioSnapshot::new(
2027            AccountId::from("SIM-001"),
2028            AccountType::Cash,
2029            Some(Currency::USD()),
2030            vec![],
2031            vec![],
2032            vec![],
2033            vec![],
2034            vec![],
2035            None,
2036            false,
2037            vec![],
2038            vec![],
2039            vec![],
2040            UUID4::new(),
2041            UnixNanos::from(8),
2042            UnixNanos::from(9),
2043        )
2044    }
2045
2046    fn position_event() -> PositionEvent {
2047        PositionEvent::PositionOpened(PositionOpened {
2048            trader_id: TraderId::from("TRADER-001"),
2049            strategy_id: StrategyId::from("S-001"),
2050            instrument_id: InstrumentId::from("AUDUSD.SIM"),
2051            position_id: PositionId::from("P-001"),
2052            account_id: AccountId::from("SIM-001"),
2053            opening_order_id: ClientOrderId::from("O-19700101-000000-001-001-1"),
2054            entry: OrderSide::Buy,
2055            side: PositionSide::Long,
2056            signed_qty: 100.0,
2057            quantity: Quantity::from("100"),
2058            last_qty: Quantity::from("100"),
2059            last_px: Price::from("1.00000"),
2060            currency: Currency::USD(),
2061            avg_px_open: 1.0,
2062            realized_pnl: None,
2063            event_id: UUID4::new(),
2064            ts_event: UnixNanos::from(10),
2065            ts_init: UnixNanos::from(11),
2066        })
2067    }
2068
2069    #[cfg(feature = "defi")]
2070    fn defi_chain() -> Arc<Chain> {
2071        Arc::new(
2072            Chain::from_chain_id(42161)
2073                .expect("Arbitrum chain must be registered")
2074                .clone(),
2075        )
2076    }
2077
2078    #[cfg(feature = "defi")]
2079    fn defi_dex() -> Arc<Dex> {
2080        let chain = Chain::from_chain_id(42161)
2081            .expect("Arbitrum chain must be registered")
2082            .clone();
2083        Arc::new(Dex::new(
2084            chain,
2085            DexType::UniswapV3,
2086            "0x1F98431c8aD98523631AE4a59f267346ea31F984",
2087            0,
2088            AmmType::CLAMM,
2089            "PoolCreated",
2090            "Swap",
2091            "Mint",
2092            "Burn",
2093            "Collect",
2094        ))
2095    }
2096
2097    #[cfg(feature = "defi")]
2098    fn defi_pool() -> Pool {
2099        let chain = defi_chain();
2100        let dex = defi_dex();
2101        let rain = Token::new(
2102            chain.clone(),
2103            address!("0x25118290e6A5f4139381D072181157035864099d"),
2104            "RAIN".to_string(),
2105            "RAIN".to_string(),
2106            18,
2107        );
2108        let weth = Token::new(
2109            chain.clone(),
2110            address!("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
2111            "Wrapped Ether".to_string(),
2112            "WETH".to_string(),
2113            18,
2114        );
2115        let pool_address = address!("0xd13040d4fe917EE704158CfCB3338dCd2838B245");
2116
2117        Pool::new(
2118            chain,
2119            dex,
2120            pool_address,
2121            PoolIdentifier::from_address(pool_address),
2122            0,
2123            rain,
2124            weth,
2125            Some(3000),
2126            Some(60),
2127            UnixNanos::from(12),
2128        )
2129    }
2130
2131    #[cfg(feature = "defi")]
2132    fn defi_block() -> Block {
2133        Block::new(
2134            "0x0000000000000000000000000000000000000000000000000000000000000100".to_string(),
2135            "0x0000000000000000000000000000000000000000000000000000000000000099".to_string(),
2136            100,
2137            Ustr::from("0x0000000000000000000000000000000000000001"),
2138            30_000_000,
2139            21_000,
2140            UnixNanos::from(13),
2141            None,
2142        )
2143    }
2144
2145    #[cfg(feature = "defi")]
2146    fn defi_transaction_hash() -> String {
2147        "0x1aa3506e78dd6e7e53986fa310c7ef1b7825042e19693c04eb56b2404067407b".to_string()
2148    }
2149
2150    #[cfg(feature = "defi")]
2151    fn defi_liquidity_update() -> PoolLiquidityUpdate {
2152        let pool = defi_pool();
2153        PoolLiquidityUpdate::new(
2154            pool.chain.clone(),
2155            pool.dex.clone(),
2156            pool.instrument_id,
2157            pool.pool_identifier,
2158            PoolLiquidityUpdateType::Mint,
2159            100_000,
2160            defi_transaction_hash(),
2161            0,
2162            1,
2163            None,
2164            address!("0x5E325eDA8064b456f4781070C0738d849c824258"),
2165            100,
2166            U256::from(10),
2167            U256::from(20),
2168            -120,
2169            120,
2170            UnixNanos::from(14),
2171            UnixNanos::from(15),
2172        )
2173    }
2174
2175    #[cfg(feature = "defi")]
2176    fn defi_collect() -> PoolFeeCollect {
2177        let pool = defi_pool();
2178        PoolFeeCollect::new(
2179            pool.chain.clone(),
2180            pool.dex.clone(),
2181            pool.instrument_id,
2182            pool.pool_identifier,
2183            100_000,
2184            defi_transaction_hash(),
2185            0,
2186            2,
2187            address!("0x5E325eDA8064b456f4781070C0738d849c824258"),
2188            10,
2189            20,
2190            -120,
2191            120,
2192            UnixNanos::from(16),
2193            UnixNanos::from(17),
2194        )
2195    }
2196
2197    #[cfg(feature = "defi")]
2198    fn defi_flash() -> PoolFlash {
2199        let pool = defi_pool();
2200        PoolFlash::new(
2201            pool.chain.clone(),
2202            pool.dex.clone(),
2203            pool.instrument_id,
2204            pool.pool_identifier,
2205            100_000,
2206            defi_transaction_hash(),
2207            0,
2208            3,
2209            UnixNanos::from(18),
2210            UnixNanos::from(19),
2211            address!("0x1aa3506e78dd6e7e53986fa310c7ef1b7825042e"),
2212            address!("0x1aa3506e78dd6e7e53986fa310c7ef1b7825042e"),
2213            U256::from(100),
2214            U256::from(200),
2215            U256::from(101),
2216            U256::from(202),
2217        )
2218    }
2219
2220    fn assert_publishable_json_msgpack_round_trips(encoding: SerializationEncoding) {
2221        assert_typed_external_round_trips(
2222            encoding,
2223            BusPayloadType::Instrument,
2224            "data.instruments.AUDUSD.SIM",
2225            InstrumentAny::CurrencyPair(audusd_sim()),
2226            publish_instrument,
2227            subscribe_instruments,
2228            assert_json_value_eq,
2229        );
2230        assert_typed_external_round_trips(
2231            encoding,
2232            BusPayloadType::OrderBookDeltas,
2233            "data.book.deltas.AAPL.XNAS",
2234            stub_deltas(),
2235            publish_deltas,
2236            subscribe_book_deltas,
2237            assert_eq_ref,
2238        );
2239        assert_typed_external_round_trips(
2240            encoding,
2241            BusPayloadType::OrderBookDepth10,
2242            "data.book.depth10.AAPL.XNAS",
2243            stub_depth10(),
2244            publish_depth10,
2245            subscribe_book_depth10,
2246            assert_depth10_market_eq,
2247        );
2248        assert_typed_external_round_trips(
2249            encoding,
2250            BusPayloadType::TradeTick,
2251            "data.trades.AUDUSD.SIM",
2252            TradeTick::default(),
2253            publish_trade,
2254            subscribe_trades,
2255            assert_eq_ref,
2256        );
2257        assert_typed_external_round_trips(
2258            encoding,
2259            BusPayloadType::Bar,
2260            "data.bars.AUDUSD.SIM",
2261            Bar::default(),
2262            publish_bar,
2263            subscribe_bars,
2264            assert_eq_ref,
2265        );
2266        assert_typed_external_round_trips(
2267            encoding,
2268            BusPayloadType::MarkPriceUpdate,
2269            "data.mark_prices.AUDUSD.SIM",
2270            mark_price_update(),
2271            publish_mark_price,
2272            subscribe_mark_prices,
2273            assert_eq_ref,
2274        );
2275        assert_typed_external_round_trips(
2276            encoding,
2277            BusPayloadType::IndexPriceUpdate,
2278            "data.index_prices.AUDUSD.SIM",
2279            index_price_update(),
2280            publish_index_price,
2281            subscribe_index_prices,
2282            assert_eq_ref,
2283        );
2284        assert_typed_external_round_trips(
2285            encoding,
2286            BusPayloadType::FundingRateUpdate,
2287            "data.funding_rates.AUDUSD.SIM",
2288            funding_rate_update(),
2289            publish_funding_rate,
2290            subscribe_funding_rates,
2291            assert_eq_ref,
2292        );
2293        assert_typed_external_round_trips(
2294            encoding,
2295            BusPayloadType::OptionGreeks,
2296            "data.option_greeks.AUDUSD.SIM",
2297            OptionGreeks::default(),
2298            publish_option_greeks,
2299            subscribe_option_greeks,
2300            assert_eq_ref,
2301        );
2302        assert_typed_external_round_trips(
2303            encoding,
2304            BusPayloadType::AccountState,
2305            "events.account.SIM-001",
2306            nautilus_model::events::account::stubs::cash_account_state(),
2307            publish_account_state,
2308            subscribe_account_state,
2309            assert_eq_ref,
2310        );
2311        assert_typed_external_round_trips(
2312            encoding,
2313            BusPayloadType::PortfolioSnapshot,
2314            "events.portfolio.SIM-001",
2315            portfolio_snapshot(),
2316            publish_portfolio_snapshot,
2317            subscribe_portfolio_snapshot,
2318            assert_eq_ref,
2319        );
2320        assert_typed_external_round_trips(
2321            encoding,
2322            BusPayloadType::OrderEvent,
2323            "events.orders.SIM-001",
2324            OrderEventAny::Denied(OrderDeniedSpec::builder().build()),
2325            publish_order_event,
2326            subscribe_order_events,
2327            assert_eq_ref,
2328        );
2329        assert_typed_external_round_trips(
2330            encoding,
2331            BusPayloadType::PositionEvent,
2332            "events.positions.SIM-001",
2333            position_event(),
2334            publish_position_event,
2335            subscribe_position_events,
2336            assert_json_value_eq,
2337        );
2338    }
2339
2340    #[cfg(feature = "defi")]
2341    fn assert_publishable_defi_json_msgpack_round_trips(encoding: SerializationEncoding) {
2342        assert_typed_external_round_trips(
2343            encoding,
2344            BusPayloadType::Block,
2345            "data.defi.blocks.ARBITRUM",
2346            defi_block(),
2347            publish_defi_block,
2348            subscribe_defi_blocks,
2349            assert_json_value_eq,
2350        );
2351        assert_typed_external_round_trips(
2352            encoding,
2353            BusPayloadType::Pool,
2354            "data.defi.pools.RAIN-WETH",
2355            defi_pool(),
2356            publish_defi_pool,
2357            subscribe_defi_pools,
2358            assert_json_value_eq,
2359        );
2360        assert_typed_external_round_trips(
2361            encoding,
2362            BusPayloadType::PoolLiquidityUpdate,
2363            "data.defi.liquidity.RAIN-WETH",
2364            defi_liquidity_update(),
2365            publish_defi_liquidity,
2366            subscribe_defi_liquidity,
2367            assert_json_value_eq,
2368        );
2369        assert_typed_external_round_trips(
2370            encoding,
2371            BusPayloadType::PoolFeeCollect,
2372            "data.defi.collects.RAIN-WETH",
2373            defi_collect(),
2374            publish_defi_collect,
2375            subscribe_defi_collects,
2376            assert_json_value_eq,
2377        );
2378        assert_typed_external_round_trips(
2379            encoding,
2380            BusPayloadType::PoolFlash,
2381            "data.defi.flash.RAIN-WETH",
2382            defi_flash(),
2383            publish_defi_flash,
2384            subscribe_defi_flash,
2385            assert_json_value_eq,
2386        );
2387    }
2388
2389    #[rstest]
2390    #[case(SerializationEncoding::Json)]
2391    #[case(SerializationEncoding::MsgPack)]
2392    fn republish_external_message_round_trips_publishable_json_msgpack(
2393        #[case] encoding: SerializationEncoding,
2394    ) {
2395        assert_publishable_json_msgpack_round_trips(encoding);
2396    }
2397
2398    #[cfg(feature = "defi")]
2399    #[rstest]
2400    #[case(SerializationEncoding::Json)]
2401    #[case(SerializationEncoding::MsgPack)]
2402    fn republish_external_message_round_trips_publishable_defi_json_msgpack(
2403        #[case] encoding: SerializationEncoding,
2404    ) {
2405        assert_publishable_defi_json_msgpack_round_trips(encoding);
2406    }
2407
2408    fn assert_custom_data_round_trips(encoding: SerializationEncoding) {
2409        let publications = install_capturing_external_egress(encoding);
2410        let custom = stub_custom_data(100, 42, None, Some("stub-id".to_string()));
2411
2412        publish_any("data.custom.StubCustomData".into(), &custom);
2413
2414        let bus_message = {
2415            let publications = publications.borrow();
2416            assert_eq!(publications.len(), 1);
2417            assert_eq!(
2418                publications[0].payload_type,
2419                BusPayloadType::Custom(Ustr::from("StubCustomData"))
2420            );
2421            assert_eq!(publications[0].encoding, encoding);
2422            BusMessage::with_str_topic(
2423                publications[0].topic.clone(),
2424                publications[0].payload_type,
2425                publications[0].payload.clone(),
2426                publications[0].encoding,
2427            )
2428        };
2429        publications.borrow_mut().clear();
2430
2431        let received = Rc::new(RefCell::new(Vec::<CustomData>::new()));
2432        let received_handler = received.clone();
2433        subscribe_any(
2434            "data.custom.StubCustomData".into(),
2435            ShareableMessageHandler::from_typed(move |message: &CustomData| {
2436                received_handler.borrow_mut().push(message.clone());
2437            }),
2438            None,
2439        );
2440
2441        get_message_bus()
2442            .borrow_mut()
2443            .add_streaming_type(BusPayloadType::Custom(Ustr::from("StubCustomData")));
2444        republish_external_message(&bus_message).unwrap();
2445
2446        assert_eq!(*received.borrow(), vec![custom]);
2447        assert!(
2448            publications.borrow().is_empty(),
2449            "republished message must not be forwarded back out externally"
2450        );
2451        reset_message_bus();
2452    }
2453
2454    #[rstest]
2455    #[case(SerializationEncoding::Json)]
2456    #[case(SerializationEncoding::MsgPack)]
2457    fn republish_external_message_round_trips_custom_data(#[case] encoding: SerializationEncoding) {
2458        assert_custom_data_round_trips(encoding);
2459    }
2460
2461    #[rstest]
2462    #[case(SerializationEncoding::Json)]
2463    #[case(SerializationEncoding::MsgPack)]
2464    fn republish_external_message_skips_unregistered_custom_payload(
2465        #[case] encoding: SerializationEncoding,
2466    ) {
2467        let envelope = serde_json::json!({
2468            "type": "UnregisteredCustomData",
2469            "data_type": {
2470                "type_name": "UnregisteredCustomData",
2471                "metadata": {},
2472            },
2473            "payload": {
2474                "value": 1,
2475            },
2476        });
2477        let payload = match encoding {
2478            SerializationEncoding::Json => {
2479                serde_json::to_vec(&envelope).expect("JSON envelope must serialize")
2480            }
2481            SerializationEncoding::MsgPack => {
2482                rmp_serde::to_vec_named(&envelope).expect("MsgPack envelope must serialize")
2483            }
2484            SerializationEncoding::Sbe | SerializationEncoding::Capnp => {
2485                unreachable!("schema encodings do not support custom payloads")
2486            }
2487        };
2488        let message = BusMessage::with_str_topic(
2489            "data.custom.UnregisteredCustomData",
2490            BusPayloadType::Custom(Ustr::from("UnregisteredCustomData")),
2491            Bytes::from(payload),
2492            encoding,
2493        );
2494
2495        get_message_bus()
2496            .borrow_mut()
2497            .add_streaming_type(BusPayloadType::Custom(Ustr::from("UnregisteredCustomData")));
2498        republish_external_message(&message).unwrap();
2499        reset_message_bus();
2500    }
2501
2502    #[rstest]
2503    fn republish_external_message_skips_untyped_custom_payload() {
2504        let message = BusMessage::with_str_topic(
2505            "events/control",
2506            BusPayloadType::Custom(Ustr::default()),
2507            Bytes::new(),
2508            SerializationEncoding::Json,
2509        );
2510
2511        republish_external_message(&message).unwrap();
2512        reset_message_bus();
2513    }
2514
2515    #[rstest]
2516    fn republish_external_message_skips_unregistered_streaming_type_before_decode() {
2517        let received = Rc::new(RefCell::new(Vec::<QuoteTick>::new()));
2518        let received_handler = received.clone();
2519        let handler = TypedHandler::from(move |quote: &QuoteTick| {
2520            received_handler.borrow_mut().push(*quote);
2521        });
2522        subscribe_quotes("data.quotes.*".into(), handler, None);
2523
2524        let message = BusMessage::with_str_topic(
2525            "data.quotes.AUDUSD.SIM",
2526            BusPayloadType::QuoteTick,
2527            Bytes::from_static(b"not-json"),
2528            SerializationEncoding::Json,
2529        );
2530
2531        republish_external_message(&message).unwrap();
2532
2533        assert!(received.borrow().is_empty());
2534        reset_message_bus();
2535    }
2536
2537    #[cfg(any(feature = "sbe", feature = "capnp"))]
2538    fn assert_market_data_binary_round_trips(encoding: SerializationEncoding) {
2539        assert_typed_external_round_trips(
2540            encoding,
2541            BusPayloadType::OrderBookDeltas,
2542            "data.book.deltas.AAPL.XNAS",
2543            stub_deltas(),
2544            publish_deltas,
2545            subscribe_book_deltas,
2546            assert_eq_ref,
2547        );
2548        assert_typed_external_round_trips(
2549            encoding,
2550            BusPayloadType::OrderBookDepth10,
2551            "data.book.depth10.AAPL.XNAS",
2552            stub_depth10(),
2553            publish_depth10,
2554            subscribe_book_depth10,
2555            assert_depth10_market_eq,
2556        );
2557        assert_typed_external_round_trips(
2558            encoding,
2559            BusPayloadType::QuoteTick,
2560            "data.quotes.AUDUSD.SIM",
2561            QuoteTick::default(),
2562            publish_quote,
2563            subscribe_quotes,
2564            assert_eq_ref,
2565        );
2566        assert_typed_external_round_trips(
2567            encoding,
2568            BusPayloadType::TradeTick,
2569            "data.trades.AUDUSD.SIM",
2570            TradeTick::default(),
2571            publish_trade,
2572            subscribe_trades,
2573            assert_eq_ref,
2574        );
2575        assert_typed_external_round_trips(
2576            encoding,
2577            BusPayloadType::Bar,
2578            "data.bars.AUDUSD.SIM",
2579            Bar::default(),
2580            publish_bar,
2581            subscribe_bars,
2582            assert_eq_ref,
2583        );
2584        assert_typed_external_round_trips(
2585            encoding,
2586            BusPayloadType::MarkPriceUpdate,
2587            "data.mark_prices.AUDUSD.SIM",
2588            mark_price_update(),
2589            publish_mark_price,
2590            subscribe_mark_prices,
2591            assert_eq_ref,
2592        );
2593        assert_typed_external_round_trips(
2594            encoding,
2595            BusPayloadType::IndexPriceUpdate,
2596            "data.index_prices.AUDUSD.SIM",
2597            index_price_update(),
2598            publish_index_price,
2599            subscribe_index_prices,
2600            assert_eq_ref,
2601        );
2602        assert_typed_external_round_trips(
2603            encoding,
2604            BusPayloadType::FundingRateUpdate,
2605            "data.funding_rates.AUDUSD.SIM",
2606            funding_rate_update(),
2607            publish_funding_rate,
2608            subscribe_funding_rates,
2609            assert_eq_ref,
2610        );
2611        assert_typed_external_round_trips(
2612            encoding,
2613            BusPayloadType::OptionGreeks,
2614            "data.option_greeks.BTC-30JUN23-40000-C.DERIBIT",
2615            option_greeks(),
2616            publish_option_greeks,
2617            subscribe_option_greeks,
2618            assert_eq_ref,
2619        );
2620    }
2621
2622    #[cfg(feature = "sbe")]
2623    #[rstest]
2624    fn republish_external_message_round_trips_market_data_sbe() {
2625        assert_market_data_binary_round_trips(SerializationEncoding::Sbe);
2626    }
2627
2628    #[cfg(feature = "capnp")]
2629    #[rstest]
2630    fn republish_external_message_round_trips_market_data_capnp() {
2631        assert_market_data_binary_round_trips(SerializationEncoding::Capnp);
2632    }
2633
2634    #[rstest]
2635    #[case(BusPayloadType::AccountState)]
2636    fn republish_external_message_skips_unsupported_binary_payload(
2637        #[case] payload_type: BusPayloadType,
2638    ) {
2639        let received = Rc::new(RefCell::new(Vec::<serde_json::Value>::new()));
2640        let account_received = received.clone();
2641        subscribe_account_state(
2642            "events.unsupported.*".into(),
2643            TypedHandler::from(move |state: &AccountState| {
2644                account_received
2645                    .borrow_mut()
2646                    .push(serde_json::to_value(state).unwrap());
2647            }),
2648            None,
2649        );
2650
2651        for encoding in [SerializationEncoding::Sbe, SerializationEncoding::Capnp] {
2652            let message = BusMessage::with_str_topic(
2653                "events.unsupported.payload",
2654                payload_type,
2655                Bytes::from_static(b"malformed unsupported payload"),
2656                encoding,
2657            );
2658            get_message_bus()
2659                .borrow_mut()
2660                .add_streaming_type(payload_type);
2661            republish_external_message(&message).unwrap();
2662        }
2663
2664        assert!(received.borrow().is_empty());
2665        reset_message_bus();
2666    }
2667
2668    #[rstest]
2669    fn republish_external_message_errors_for_malformed_supported_payload() {
2670        let message = BusMessage::with_str_topic(
2671            "data.quotes.AUDUSD.SIM",
2672            BusPayloadType::QuoteTick,
2673            Bytes::from_static(b"not-json"),
2674            SerializationEncoding::Json,
2675        );
2676
2677        get_message_bus()
2678            .borrow_mut()
2679            .add_streaming_type(BusPayloadType::QuoteTick);
2680        let error = republish_external_message(&message).unwrap_err();
2681
2682        assert!(
2683            error
2684                .to_string()
2685                .contains("failed to decode JSON QuoteTick"),
2686            "{error:?}"
2687        );
2688        reset_message_bus();
2689    }
2690
2691    #[rstest]
2692    fn republish_external_message_rejects_invalid_topic_and_processes_next_message() {
2693        let quote = QuoteTick::default();
2694        let payload = serde_json::to_vec(&quote).unwrap();
2695        let invalid_message = BusMessage::with_str_topic(
2696            "data.quotes.AUDUSD.SIM*",
2697            BusPayloadType::Custom(Ustr::from("UnregisteredCustomData")),
2698            Bytes::from(payload.clone()),
2699            SerializationEncoding::Json,
2700        );
2701        let valid_message = BusMessage::with_str_topic(
2702            "data.quotes.AUDUSD.SIM",
2703            BusPayloadType::QuoteTick,
2704            Bytes::from(payload),
2705            SerializationEncoding::Json,
2706        );
2707        let received = Rc::new(RefCell::new(Vec::<QuoteTick>::new()));
2708        let received_handler = received.clone();
2709        subscribe_quotes(
2710            "data.quotes.*".into(),
2711            TypedHandler::from(move |quote: &QuoteTick| {
2712                received_handler.borrow_mut().push(*quote);
2713            }),
2714            None,
2715        );
2716        get_message_bus()
2717            .borrow_mut()
2718            .add_streaming_type(BusPayloadType::QuoteTick);
2719
2720        let error = republish_external_message(&invalid_message).unwrap_err();
2721        republish_external_message(&valid_message).unwrap();
2722
2723        assert_eq!(
2724            format!("{error:#}"),
2725            "invalid external message topic: Topic `value` contained invalid characters, was data.quotes.AUDUSD.SIM*"
2726        );
2727        assert_eq!(*received.borrow(), vec![quote]);
2728        reset_message_bus();
2729    }
2730
2731    #[cfg(feature = "sbe")]
2732    #[rstest]
2733    fn publish_quote_sbe_forwards_decodable_payload_to_external_egress() {
2734        let publications = install_capturing_external_egress(SerializationEncoding::Sbe);
2735        let quote = QuoteTick::default();
2736
2737        publish_quote("data.quotes.TEST".into(), &quote);
2738
2739        let publications = publications.borrow();
2740        assert_eq!(publications.len(), 1);
2741        assert_eq!(publications[0].topic, "data.quotes.TEST");
2742        assert_eq!(
2743            QuoteTick::from_sbe(&publications[0].payload)
2744                .expect("SBE payload must decode as QuoteTick"),
2745            quote
2746        );
2747        drop(publications);
2748        reset_message_bus();
2749    }
2750
2751    #[cfg(feature = "sbe")]
2752    #[rstest]
2753    fn publish_option_greeks_sbe_forwards_decodable_payload_to_external_egress() {
2754        let publications = install_capturing_external_egress(SerializationEncoding::Sbe);
2755        let greeks = option_greeks();
2756
2757        publish_option_greeks("data.option_greeks.TEST".into(), &greeks);
2758
2759        let publications = publications.borrow();
2760        assert_eq!(publications.len(), 1);
2761        assert_eq!(publications[0].topic, "data.option_greeks.TEST");
2762        assert_eq!(
2763            OptionGreeks::from_sbe(&publications[0].payload)
2764                .expect("SBE payload must decode as OptionGreeks"),
2765            greeks
2766        );
2767        drop(publications);
2768        reset_message_bus();
2769    }
2770
2771    #[cfg(not(feature = "sbe"))]
2772    #[rstest]
2773    fn publish_quote_sbe_without_feature_drops_payload() {
2774        let publications = install_capturing_external_egress(SerializationEncoding::Sbe);
2775        let quote = QuoteTick::default();
2776
2777        publish_quote("data.quotes.TEST".into(), &quote);
2778
2779        assert!(publications.borrow().is_empty());
2780        reset_message_bus();
2781    }
2782
2783    #[cfg(feature = "capnp")]
2784    #[rstest]
2785    fn publish_quote_capnp_forwards_decodable_payload_to_external_egress() {
2786        let publications = install_capturing_external_egress(SerializationEncoding::Capnp);
2787        let quote = QuoteTick::default();
2788
2789        publish_quote("data.quotes.TEST".into(), &quote);
2790
2791        let publications = publications.borrow();
2792        assert_eq!(publications.len(), 1);
2793        assert_eq!(publications[0].topic, "data.quotes.TEST");
2794        let reader = capnp::serialize::read_message(
2795            &mut &publications[0].payload[..],
2796            capnp::message::ReaderOptions::new(),
2797        )
2798        .expect("Cap'n Proto payload must be readable");
2799        let root = reader
2800            .get_root::<market_capnp::quote_tick::Reader>()
2801            .expect("Cap'n Proto payload must have a QuoteTick root");
2802        let decoded =
2803            QuoteTick::from_capnp(root).expect("Cap'n Proto payload must decode as QuoteTick");
2804        assert_eq!(decoded, quote);
2805        drop(publications);
2806        reset_message_bus();
2807    }
2808
2809    #[cfg(feature = "capnp")]
2810    #[rstest]
2811    fn publish_option_greeks_capnp_forwards_decodable_payload_to_external_egress() {
2812        let publications = install_capturing_external_egress(SerializationEncoding::Capnp);
2813        let greeks = option_greeks();
2814
2815        publish_option_greeks("data.option_greeks.TEST".into(), &greeks);
2816
2817        let publications = publications.borrow();
2818        assert_eq!(publications.len(), 1);
2819        assert_eq!(publications[0].topic, "data.option_greeks.TEST");
2820        let reader = capnp::serialize::read_message(
2821            &mut &publications[0].payload[..],
2822            capnp::message::ReaderOptions::new(),
2823        )
2824        .expect("Cap'n Proto payload must be readable");
2825        let root = reader
2826            .get_root::<market_capnp::option_greeks::Reader>()
2827            .expect("Cap'n Proto payload must have an OptionGreeks root");
2828        let decoded = OptionGreeks::from_capnp(root)
2829            .expect("Cap'n Proto payload must decode as OptionGreeks");
2830        assert_eq!(decoded, greeks);
2831        drop(publications);
2832        reset_message_bus();
2833    }
2834
2835    #[cfg(not(feature = "capnp"))]
2836    #[rstest]
2837    fn publish_quote_capnp_without_feature_drops_payload() {
2838        let publications = install_capturing_external_egress(SerializationEncoding::Capnp);
2839        let quote = QuoteTick::default();
2840
2841        publish_quote("data.quotes.TEST".into(), &quote);
2842
2843        assert!(publications.borrow().is_empty());
2844        reset_message_bus();
2845    }
2846
2847    #[rstest]
2848    fn publish_quote_external_egress_respects_filter_and_suppress_guard() {
2849        let publications = install_capturing_external_egress(SerializationEncoding::MsgPack);
2850        let quote = QuoteTick::default();
2851
2852        get_message_bus()
2853            .borrow_mut()
2854            .set_types_filter(vec!["QuoteTick".to_string()]);
2855        publish_quote("data.quotes.FILTERED".into(), &quote);
2856
2857        get_message_bus().borrow_mut().set_types_filter(Vec::new());
2858        {
2859            let _guard = SuppressExternalGuard::new();
2860            publish_quote("data.quotes.SUPPRESSED".into(), &quote);
2861        }
2862        publish_quote("data.quotes.PUBLISHED".into(), &quote);
2863
2864        let publications = publications.borrow();
2865        assert_eq!(publications.len(), 1);
2866        assert_eq!(publications[0].topic, "data.quotes.PUBLISHED");
2867        drop(publications);
2868        reset_message_bus();
2869    }
2870
2871    #[rstest]
2872    fn publish_quote_uses_market_data_encoding_override() {
2873        let publications = install_capturing_external_egress_config(&MessageBusConfig {
2874            encoding: SerializationEncoding::Json,
2875            encoding_market_data: Some(SerializationEncoding::MsgPack),
2876            ..Default::default()
2877        });
2878        let quote = QuoteTick::default();
2879
2880        publish_quote("data.quotes.TEST".into(), &quote);
2881
2882        let publications = publications.borrow();
2883        assert_eq!(publications.len(), 1);
2884        assert_eq!(publications[0].encoding, SerializationEncoding::MsgPack);
2885        assert_eq!(
2886            rmp_serde::from_slice::<QuoteTick>(&publications[0].payload)
2887                .expect("MsgPack payload must decode as QuoteTick"),
2888            quote
2889        );
2890        drop(publications);
2891        reset_message_bus();
2892    }
2893
2894    #[rstest]
2895    fn publish_custom_data_forwards_envelope_to_external_egress_and_respects_filter() {
2896        let publications = install_capturing_external_egress(SerializationEncoding::Json);
2897        let custom = stub_custom_data(100, 42, None, Some("stub-id".to_string()));
2898
2899        publish_any("data.custom.StubCustomData".into(), &custom);
2900
2901        get_message_bus()
2902            .borrow_mut()
2903            .set_types_filter(vec!["StubCustomData".to_string()]);
2904        publish_any("data.custom.FILTERED".into(), &custom);
2905
2906        let publications = publications.borrow();
2907        assert_eq!(publications.len(), 1);
2908        assert_eq!(publications[0].topic, "data.custom.StubCustomData");
2909
2910        let payload_value: serde_json::Value = serde_json::from_slice(&publications[0].payload)
2911            .expect("JSON payload must decode as a CustomData envelope");
2912        assert_eq!(
2913            payload_value.get("type").and_then(|value| value.as_str()),
2914            Some("StubCustomData")
2915        );
2916        assert_eq!(
2917            payload_value
2918                .pointer("/data_type/type_name")
2919                .and_then(|value| value.as_str()),
2920            Some("StubCustomData")
2921        );
2922        assert_eq!(
2923            payload_value
2924                .pointer("/data_type/identifier")
2925                .and_then(|value| value.as_str()),
2926            Some("stub-id")
2927        );
2928        assert_eq!(
2929            payload_value
2930                .pointer("/payload/value")
2931                .and_then(serde_json::Value::as_i64),
2932            Some(42)
2933        );
2934        drop(publications);
2935        reset_message_bus();
2936    }
2937
2938    #[rstest]
2939    fn test_typed_quote_publish_subscribe_integration() {
2940        let msgbus = get_message_bus();
2941        let pub_count = msgbus.borrow().pub_count();
2942        let received = Rc::new(RefCell::new(Vec::new()));
2943        let received_clone = received.clone();
2944
2945        let handler = TypedHandler::from(move |quote: &QuoteTick| {
2946            received_clone.borrow_mut().push(*quote);
2947        });
2948
2949        subscribe_quotes("data.quotes.*".into(), handler, None);
2950
2951        let quote = QuoteTick::default();
2952        publish_quote("data.quotes.TEST".into(), &quote);
2953        publish_quote("data.quotes.TEST".into(), &quote);
2954
2955        assert_eq!(received.borrow().len(), 2);
2956        assert_eq!(msgbus.borrow().pub_count(), pub_count + 2);
2957    }
2958
2959    #[rstest]
2960    fn test_typed_trade_publish_subscribe_integration() {
2961        let _msgbus = get_message_bus();
2962        let received = Rc::new(RefCell::new(Vec::new()));
2963        let received_clone = received.clone();
2964
2965        let handler = TypedHandler::from(move |trade: &TradeTick| {
2966            received_clone.borrow_mut().push(*trade);
2967        });
2968
2969        subscribe_trades("data.trades.*".into(), handler, None);
2970
2971        let trade = TradeTick::default();
2972        publish_trade("data.trades.TEST".into(), &trade);
2973
2974        assert_eq!(received.borrow().len(), 1);
2975    }
2976
2977    #[rstest]
2978    fn test_typed_bar_publish_subscribe_integration() {
2979        let _msgbus = get_message_bus();
2980        let received = Rc::new(RefCell::new(Vec::new()));
2981        let received_clone = received.clone();
2982
2983        let handler = TypedHandler::from(move |bar: &Bar| {
2984            received_clone.borrow_mut().push(*bar);
2985        });
2986
2987        subscribe_bars("data.bars.*".into(), handler, None);
2988
2989        let bar = Bar::default();
2990        publish_bar("data.bars.TEST".into(), &bar);
2991
2992        assert_eq!(received.borrow().len(), 1);
2993    }
2994
2995    #[rstest]
2996    fn test_typed_deltas_publish_subscribe_integration() {
2997        let _msgbus = get_message_bus();
2998        let received = Rc::new(RefCell::new(Vec::new()));
2999        let received_clone = received.clone();
3000
3001        let handler = TypedHandler::from(move |deltas: &OrderBookDeltas| {
3002            received_clone.borrow_mut().push(deltas.clone());
3003        });
3004
3005        subscribe_book_deltas("data.book.deltas.*".into(), handler, None);
3006
3007        let instrument_id = InstrumentId::from("TEST.VENUE");
3008        let delta = OrderBookDelta::clear(instrument_id, 0, 1.into(), 2.into());
3009        let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
3010        publish_deltas("data.book.deltas.TEST".into(), &deltas);
3011
3012        assert_eq!(received.borrow().len(), 1);
3013    }
3014
3015    #[rstest]
3016    fn test_typed_unsubscribe_stops_delivery() {
3017        let _msgbus = get_message_bus();
3018        let received = Rc::new(RefCell::new(Vec::new()));
3019        let received_clone = received.clone();
3020
3021        let handler = TypedHandler::from_with_id("unsub-test", move |quote: &QuoteTick| {
3022            received_clone.borrow_mut().push(*quote);
3023        });
3024
3025        subscribe_quotes("data.quotes.UNSUB".into(), handler.clone(), None);
3026
3027        let quote = QuoteTick::default();
3028        publish_quote("data.quotes.UNSUB".into(), &quote);
3029        assert_eq!(received.borrow().len(), 1);
3030
3031        unsubscribe_quotes("data.quotes.UNSUB".into(), &handler);
3032
3033        publish_quote("data.quotes.UNSUB".into(), &quote);
3034        assert_eq!(received.borrow().len(), 1);
3035    }
3036
3037    #[rstest]
3038    fn test_typed_wildcard_pattern_matching() {
3039        let _msgbus = get_message_bus();
3040        let received = Rc::new(RefCell::new(Vec::new()));
3041        let received_clone = received.clone();
3042
3043        let handler = TypedHandler::from(move |quote: &QuoteTick| {
3044            received_clone.borrow_mut().push(*quote);
3045        });
3046
3047        subscribe_quotes("data.quotes.WILD.*".into(), handler, None);
3048
3049        let quote = QuoteTick::default();
3050        publish_quote("data.quotes.WILD.AAPL".into(), &quote);
3051        publish_quote("data.quotes.WILD.MSFT".into(), &quote);
3052        publish_quote("data.quotes.OTHER.AAPL".into(), &quote);
3053
3054        assert_eq!(received.borrow().len(), 2);
3055    }
3056
3057    #[rstest]
3058    fn test_typed_priority_ordering() {
3059        let _msgbus = get_message_bus();
3060        let order = Rc::new(RefCell::new(Vec::new()));
3061
3062        let order1 = order.clone();
3063        let handler_low = TypedHandler::from_with_id("low-priority", move |_: &QuoteTick| {
3064            order1.borrow_mut().push("low");
3065        });
3066
3067        let order2 = order.clone();
3068        let handler_high = TypedHandler::from_with_id("high-priority", move |_: &QuoteTick| {
3069            order2.borrow_mut().push("high");
3070        });
3071
3072        subscribe_quotes("data.quotes.PRIO.*".into(), handler_low, Some(1));
3073        subscribe_quotes("data.quotes.PRIO.*".into(), handler_high, Some(10));
3074
3075        let quote = QuoteTick::default();
3076        publish_quote("data.quotes.PRIO.TEST".into(), &quote);
3077
3078        assert_eq!(*order.borrow(), vec!["high", "low"]);
3079    }
3080
3081    #[rstest]
3082    fn test_typed_routing_isolation() {
3083        let _msgbus = get_message_bus();
3084        let quote_received = Rc::new(RefCell::new(false));
3085        let trade_received = Rc::new(RefCell::new(false));
3086
3087        let qr = quote_received.clone();
3088        let quote_handler = TypedHandler::from(move |_: &QuoteTick| {
3089            *qr.borrow_mut() = true;
3090        });
3091
3092        let tr = trade_received.clone();
3093        let trade_handler = TypedHandler::from(move |_: &TradeTick| {
3094            *tr.borrow_mut() = true;
3095        });
3096
3097        subscribe_quotes("data.iso.*".into(), quote_handler, None);
3098        subscribe_trades("data.iso.*".into(), trade_handler, None);
3099
3100        let quote = QuoteTick::default();
3101        publish_quote("data.iso.TEST".into(), &quote);
3102
3103        assert!(*quote_received.borrow());
3104        assert!(!*trade_received.borrow());
3105    }
3106
3107    #[rstest]
3108    fn test_send_data_allows_reentrant_topic_access() {
3109        use crate::msgbus::switchboard::get_quotes_topic;
3110
3111        let _msgbus = get_message_bus();
3112        let topic_retrieved = Rc::new(RefCell::new(false));
3113        let topic_clone = topic_retrieved.clone();
3114
3115        let handler = TypedIntoHandler::from(move |data: Data| {
3116            let instrument_id = data.instrument_id();
3117            let _topic = get_quotes_topic(instrument_id);
3118            *topic_clone.borrow_mut() = true;
3119        });
3120
3121        let endpoint: MStr<Endpoint> = "ReentrantTest.data".into();
3122        register_data_endpoint(endpoint, handler);
3123
3124        let quote = QuoteTick::default();
3125        send_data(endpoint, Data::Quote(quote));
3126
3127        assert!(*topic_retrieved.borrow());
3128    }
3129
3130    #[rstest]
3131    fn test_send_trading_command_allows_reentrant_topic_access() {
3132        use nautilus_model::identifiers::{StrategyId, TraderId};
3133
3134        use crate::{
3135            messages::execution::{TradingCommand, cancel::CancelAllOrders},
3136            msgbus::switchboard::get_trades_topic,
3137        };
3138
3139        let _msgbus = get_message_bus();
3140        let topic_retrieved = Rc::new(RefCell::new(false));
3141        let topic_clone = topic_retrieved.clone();
3142
3143        let handler = TypedIntoHandler::from(move |cmd: TradingCommand| {
3144            let instrument_id = cmd.instrument_id();
3145            let _topic = get_trades_topic(instrument_id);
3146            *topic_clone.borrow_mut() = true;
3147        });
3148
3149        let endpoint: MStr<Endpoint> = "ReentrantTest.tradingCmd".into();
3150        register_trading_command_endpoint(endpoint, handler);
3151
3152        let cmd = TradingCommand::CancelAllOrders(CancelAllOrders::new(
3153            TraderId::new("TESTER-001"),
3154            None,
3155            StrategyId::new("S-001"),
3156            InstrumentId::from("TEST.VENUE"),
3157            None,
3158            UUID4::new(),
3159            0.into(),
3160            None,
3161            None, // correlation_id
3162        ));
3163        send_trading_command(endpoint, cmd);
3164
3165        assert!(*topic_retrieved.borrow());
3166    }
3167
3168    #[rstest]
3169    fn test_send_account_state_allows_reentrant_topic_access() {
3170        use nautilus_model::{enums::AccountType, identifiers::AccountId, types::Currency};
3171
3172        use crate::msgbus::switchboard::get_quotes_topic;
3173
3174        let _msgbus = get_message_bus();
3175        let topic_retrieved = Rc::new(RefCell::new(false));
3176        let topic_clone = topic_retrieved.clone();
3177
3178        let handler = TypedHandler::from(move |_state: &AccountState| {
3179            let instrument_id = InstrumentId::from("TEST.VENUE");
3180            let _topic = get_quotes_topic(instrument_id);
3181            *topic_clone.borrow_mut() = true;
3182        });
3183
3184        let endpoint: MStr<Endpoint> = "ReentrantTest.accountState".into();
3185        register_account_state_endpoint(endpoint, handler);
3186
3187        let state = AccountState::new(
3188            AccountId::new("SIM-001"),
3189            AccountType::Cash,
3190            vec![],
3191            vec![],
3192            true,
3193            UUID4::new(),
3194            0.into(),
3195            0.into(),
3196            Some(Currency::USD()),
3197        );
3198        send_account_state(endpoint, &state);
3199
3200        assert!(*topic_retrieved.borrow());
3201    }
3202
3203    #[rstest]
3204    fn test_send_order_event_allows_reentrant_topic_access() {
3205        use crate::msgbus::switchboard::get_quotes_topic;
3206
3207        let _msgbus = get_message_bus();
3208        let topic_retrieved = Rc::new(RefCell::new(false));
3209        let topic_clone = topic_retrieved.clone();
3210
3211        let handler = TypedIntoHandler::from(move |_event: OrderEventAny| {
3212            let instrument_id = InstrumentId::from("TEST.VENUE");
3213            let _topic = get_quotes_topic(instrument_id);
3214            *topic_clone.borrow_mut() = true;
3215        });
3216
3217        let endpoint: MStr<Endpoint> = "ReentrantTest.orderEvent".into();
3218        register_order_event_endpoint(endpoint, handler);
3219
3220        let event = OrderEventAny::Denied(OrderDeniedSpec::builder().build());
3221        send_order_event(endpoint, event);
3222
3223        assert!(*topic_retrieved.borrow());
3224    }
3225
3226    #[rstest]
3227    fn test_send_data_command_allows_reentrant_topic_access() {
3228        use crate::msgbus::switchboard::get_trades_topic;
3229
3230        let msgbus = get_message_bus();
3231        let sent_count = msgbus.borrow().sent_count();
3232        let req_count = msgbus.borrow().req_count();
3233        let topic_retrieved = Rc::new(RefCell::new(false));
3234        let topic_clone = topic_retrieved.clone();
3235
3236        let handler = TypedIntoHandler::from(move |_cmd: DataCommand| {
3237            let _topic = get_trades_topic(InstrumentId::from("TEST.VENUE"));
3238            *topic_clone.borrow_mut() = true;
3239        });
3240
3241        let endpoint: MStr<Endpoint> = "ReentrantTest.dataCmd".into();
3242        register_data_command_endpoint(endpoint, handler);
3243
3244        let cmd = DataCommand::Subscribe(SubscribeCommand::Quotes(SubscribeQuotes::new(
3245            InstrumentId::from("TEST.VENUE"),
3246            Some(ClientId::new("SIM")),
3247            None,
3248            UUID4::new(),
3249            0.into(),
3250            None,
3251            None,
3252        )));
3253        send_data_command(endpoint, cmd);
3254
3255        assert!(*topic_retrieved.borrow());
3256        assert_eq!(msgbus.borrow().sent_count(), sent_count + 1);
3257        assert_eq!(msgbus.borrow().req_count(), req_count);
3258
3259        let request = DataCommand::Request(RequestCommand::Quotes(RequestQuotes::new(
3260            InstrumentId::from("TEST.VENUE"),
3261            None,
3262            None,
3263            None,
3264            Some(ClientId::new("SIM")),
3265            UUID4::new(),
3266            0.into(),
3267            None,
3268        )));
3269        send_data_command(endpoint, request);
3270
3271        assert_eq!(msgbus.borrow().sent_count(), sent_count + 2);
3272        assert_eq!(msgbus.borrow().req_count(), req_count + 1);
3273    }
3274
3275    #[rstest]
3276    fn test_send_data_request_without_endpoint_does_not_increment_counts() {
3277        let msgbus = get_message_bus();
3278        let sent_count = msgbus.borrow().sent_count();
3279        let req_count = msgbus.borrow().req_count();
3280
3281        let request = DataCommand::Request(RequestCommand::Quotes(RequestQuotes::new(
3282            InstrumentId::from("MISSING.VENUE"),
3283            None,
3284            None,
3285            None,
3286            Some(ClientId::new("SIM")),
3287            UUID4::new(),
3288            0.into(),
3289            None,
3290        )));
3291        send_data_command("Missing.dataCmd".into(), request);
3292
3293        assert_eq!(msgbus.borrow().sent_count(), sent_count);
3294        assert_eq!(msgbus.borrow().req_count(), req_count);
3295    }
3296
3297    #[rstest]
3298    fn test_send_data_response_allows_reentrant_topic_access() {
3299        use nautilus_model::identifiers::ClientId;
3300
3301        use crate::{
3302            messages::data::{DataResponse, QuotesResponse},
3303            msgbus::switchboard::get_quotes_topic,
3304        };
3305
3306        let _msgbus = get_message_bus();
3307        let topic_retrieved = Rc::new(RefCell::new(false));
3308        let topic_clone = topic_retrieved.clone();
3309
3310        let handler = TypedIntoHandler::from(move |_resp: DataResponse| {
3311            let _topic = get_quotes_topic(InstrumentId::from("TEST.VENUE"));
3312            *topic_clone.borrow_mut() = true;
3313        });
3314
3315        let endpoint: MStr<Endpoint> = "ReentrantTest.dataResp".into();
3316        register_data_response_endpoint(endpoint, handler);
3317
3318        let resp = DataResponse::Quotes(QuotesResponse {
3319            correlation_id: UUID4::new(),
3320            client_id: ClientId::new("SIM"),
3321            instrument_id: InstrumentId::from("TEST.VENUE"),
3322            data: vec![],
3323            start: None,
3324            end: None,
3325            ts_init: 0.into(),
3326            params: None,
3327        });
3328        send_data_response(endpoint, resp);
3329
3330        assert!(*topic_retrieved.borrow());
3331    }
3332
3333    #[rstest]
3334    fn test_send_response_increments_response_count() {
3335        use nautilus_model::identifiers::ClientId;
3336
3337        use crate::messages::data::{DataResponse, QuotesResponse};
3338
3339        let msgbus = get_message_bus();
3340        let res_count = msgbus.borrow().res_count();
3341        let resp = DataResponse::Quotes(QuotesResponse {
3342            correlation_id: UUID4::new(),
3343            client_id: ClientId::new("SIM"),
3344            instrument_id: InstrumentId::from("TEST.VENUE"),
3345            data: vec![],
3346            start: None,
3347            end: None,
3348            ts_init: 0.into(),
3349            params: None,
3350        });
3351
3352        send_response(&UUID4::new(), &resp);
3353
3354        assert_eq!(msgbus.borrow().res_count(), res_count + 1);
3355    }
3356
3357    #[rstest]
3358    fn test_send_response_consumes_handler_for_all_variants() {
3359        let client_id = ClientId::new("SIM");
3360        let instrument = audusd_sim();
3361        let instrument_id = instrument.id;
3362        let venue = Venue::new("SIM");
3363        let bar_type = BarType::from("AUD/USD.SIM-1-MINUTE-LAST-EXTERNAL");
3364
3365        let correlation_id = UUID4::new();
3366        assert_response_handler_is_consumed::<CustomDataResponse>(&DataResponse::Data(
3367            CustomDataResponse::new(
3368                correlation_id,
3369                client_id,
3370                Some(venue),
3371                DataType::new("TestData", None, None),
3372                Vec::<u8>::new(),
3373                None,
3374                None,
3375                UnixNanos::default(),
3376                None,
3377            ),
3378        ));
3379
3380        let correlation_id = UUID4::new();
3381        assert_response_handler_is_consumed::<InstrumentResponse>(&DataResponse::Instrument(
3382            Box::new(InstrumentResponse::new(
3383                correlation_id,
3384                client_id,
3385                instrument_id,
3386                InstrumentAny::CurrencyPair(instrument),
3387                None,
3388                None,
3389                UnixNanos::default(),
3390                None,
3391            )),
3392        ));
3393
3394        let correlation_id = UUID4::new();
3395        assert_response_handler_is_consumed::<InstrumentsResponse>(&DataResponse::Instruments(
3396            InstrumentsResponse::new(
3397                correlation_id,
3398                client_id,
3399                venue,
3400                Vec::new(),
3401                None,
3402                None,
3403                UnixNanos::default(),
3404                None,
3405            ),
3406        ));
3407
3408        let correlation_id = UUID4::new();
3409        assert_response_handler_is_consumed::<BookResponse>(&DataResponse::Book(
3410            BookResponse::new(
3411                correlation_id,
3412                client_id,
3413                instrument_id,
3414                OrderBook::new(instrument_id, BookType::L2_MBP),
3415                None,
3416                None,
3417                UnixNanos::default(),
3418                None,
3419            ),
3420        ));
3421
3422        let correlation_id = UUID4::new();
3423        assert_response_handler_is_consumed::<BookDeltasResponse>(&DataResponse::BookDeltas(
3424            BookDeltasResponse::new(
3425                correlation_id,
3426                client_id,
3427                instrument_id,
3428                Vec::new(),
3429                None,
3430                None,
3431                UnixNanos::default(),
3432                None,
3433            ),
3434        ));
3435
3436        let correlation_id = UUID4::new();
3437        assert_response_handler_is_consumed::<BookDepthResponse>(&DataResponse::BookDepth(
3438            BookDepthResponse::new(
3439                correlation_id,
3440                client_id,
3441                instrument_id,
3442                Vec::new(),
3443                None,
3444                None,
3445                UnixNanos::default(),
3446                None,
3447            ),
3448        ));
3449
3450        let correlation_id = UUID4::new();
3451        assert_response_handler_is_consumed::<QuotesResponse>(&DataResponse::Quotes(
3452            QuotesResponse::new(
3453                correlation_id,
3454                client_id,
3455                instrument_id,
3456                Vec::new(),
3457                None,
3458                None,
3459                UnixNanos::default(),
3460                None,
3461            ),
3462        ));
3463
3464        let correlation_id = UUID4::new();
3465        assert_response_handler_is_consumed::<TradesResponse>(&DataResponse::Trades(
3466            TradesResponse::new(
3467                correlation_id,
3468                client_id,
3469                instrument_id,
3470                Vec::new(),
3471                None,
3472                None,
3473                UnixNanos::default(),
3474                None,
3475            ),
3476        ));
3477
3478        let correlation_id = UUID4::new();
3479        assert_response_handler_is_consumed::<FundingRatesResponse>(&DataResponse::FundingRates(
3480            FundingRatesResponse::new(
3481                correlation_id,
3482                client_id,
3483                instrument_id,
3484                Vec::new(),
3485                None,
3486                None,
3487                UnixNanos::default(),
3488                None,
3489            ),
3490        ));
3491
3492        let correlation_id = UUID4::new();
3493        assert_response_handler_is_consumed::<ForwardPricesResponse>(&DataResponse::ForwardPrices(
3494            ForwardPricesResponse::new(
3495                correlation_id,
3496                client_id,
3497                venue,
3498                Vec::new(),
3499                UnixNanos::default(),
3500                None,
3501            ),
3502        ));
3503
3504        let correlation_id = UUID4::new();
3505        assert_response_handler_is_consumed::<BarsResponse>(&DataResponse::Bars(
3506            BarsResponse::new(
3507                correlation_id,
3508                client_id,
3509                bar_type,
3510                Vec::new(),
3511                None,
3512                None,
3513                UnixNanos::default(),
3514                None,
3515            ),
3516        ));
3517    }
3518
3519    #[rstest]
3520    fn test_send_response_allows_reentrant_same_correlation_registration() {
3521        reset_message_bus();
3522
3523        let correlation_id = UUID4::new();
3524        let first_calls = Rc::new(Cell::new(0));
3525        let second_calls = Rc::new(Cell::new(0));
3526        let first_handler_calls = first_calls.clone();
3527        let second_handler_calls = second_calls.clone();
3528        register_response_handler(
3529            &correlation_id,
3530            ShareableMessageHandler::from_typed(move |_response: &QuotesResponse| {
3531                first_handler_calls.set(first_handler_calls.get() + 1);
3532                let second_handler_calls = second_handler_calls.clone();
3533                register_response_handler(
3534                    &correlation_id,
3535                    ShareableMessageHandler::from_typed(move |_response: &QuotesResponse| {
3536                        second_handler_calls.set(second_handler_calls.get() + 1);
3537                    }),
3538                );
3539            }),
3540        );
3541
3542        let response = DataResponse::Quotes(QuotesResponse::new(
3543            correlation_id,
3544            ClientId::new("SIM"),
3545            InstrumentId::from("TEST.VENUE"),
3546            Vec::new(),
3547            None,
3548            None,
3549            UnixNanos::default(),
3550            None,
3551        ));
3552
3553        send_response(&correlation_id, &response);
3554        assert_eq!(first_calls.get(), 1);
3555        assert_eq!(second_calls.get(), 0);
3556        assert!(
3557            get_message_bus()
3558                .borrow()
3559                .get_response_handler(&correlation_id)
3560                .is_some(),
3561        );
3562
3563        send_response(&correlation_id, &response);
3564        assert_eq!(first_calls.get(), 1);
3565        assert_eq!(second_calls.get(), 1);
3566        assert!(
3567            get_message_bus()
3568                .borrow()
3569                .get_response_handler(&correlation_id)
3570                .is_none(),
3571        );
3572    }
3573
3574    #[rstest]
3575    fn test_send_execution_report_allows_reentrant_topic_access() {
3576        use nautilus_model::{
3577            identifiers::{AccountId, ClientId, Venue},
3578            reports::ExecutionMassStatus,
3579        };
3580
3581        use crate::{messages::execution::ExecutionReport, msgbus::switchboard::get_trades_topic};
3582
3583        let _msgbus = get_message_bus();
3584        let topic_retrieved = Rc::new(RefCell::new(false));
3585        let topic_clone = topic_retrieved.clone();
3586
3587        let handler = TypedIntoHandler::from(move |_report: ExecutionReport| {
3588            let _topic = get_trades_topic(InstrumentId::from("TEST.VENUE"));
3589            *topic_clone.borrow_mut() = true;
3590        });
3591
3592        let endpoint: MStr<Endpoint> = "ReentrantTest.execReport".into();
3593        register_execution_report_endpoint(endpoint, handler);
3594
3595        let report = ExecutionReport::MassStatus(Box::new(ExecutionMassStatus::new(
3596            ClientId::new("SIM"),
3597            AccountId::new("SIM-001"),
3598            Venue::new("TEST"),
3599            0.into(),
3600            None,
3601        )));
3602        send_execution_report(endpoint, report);
3603
3604        assert!(*topic_retrieved.borrow());
3605    }
3606
3607    #[rstest]
3608    fn test_order_event_handler_can_send_trading_command() {
3609        // Tests that a handler processing an order event can send a trading command
3610        // without causing a borrow conflict. This simulates the scenario where a
3611        // strategy's on_order_accepted() handler calls cancel_order().
3612        let _msgbus = get_message_bus();
3613        let command_sent = Rc::new(RefCell::new(false));
3614        let command_sent_clone = command_sent.clone();
3615
3616        let cmd_received = Rc::new(RefCell::new(false));
3617        let cmd_received_clone = cmd_received.clone();
3618        let cmd_handler = TypedIntoHandler::from(move |_cmd: TradingCommand| {
3619            *cmd_received_clone.borrow_mut() = true;
3620        });
3621        let cmd_endpoint: MStr<Endpoint> = "ReentrantTest.execCmd".into();
3622        register_trading_command_endpoint(cmd_endpoint, cmd_handler);
3623
3624        let event_handler = TypedIntoHandler::from(move |_event: OrderEventAny| {
3625            // Simulate strategy calling cancel_order from on_order_accepted
3626            let command = TradingCommand::CancelAllOrders(CancelAllOrders::new(
3627                TraderId::new("TESTER-001"),
3628                None,
3629                StrategyId::new("S-001"),
3630                InstrumentId::from("TEST.VENUE"),
3631                Some(OrderSide::Buy),
3632                UUID4::new(),
3633                0.into(),
3634                None,
3635                None, // correlation_id
3636            ));
3637            send_trading_command(cmd_endpoint, command);
3638            *command_sent_clone.borrow_mut() = true;
3639        });
3640
3641        let event_endpoint: MStr<Endpoint> = "ReentrantTest.orderEvt".into();
3642        register_order_event_endpoint(event_endpoint, event_handler);
3643
3644        let event = OrderEventAny::Denied(OrderDeniedSpec::builder().build());
3645        send_order_event(event_endpoint, event);
3646
3647        assert!(
3648            *command_sent.borrow(),
3649            "Order event handler should have run"
3650        );
3651        assert!(
3652            *cmd_received.borrow(),
3653            "Trading command should have been received"
3654        );
3655    }
3656
3657    #[rstest]
3658    fn test_data_handler_can_send_data_command() {
3659        // Tests that a handler processing data can send a data command
3660        // without causing a borrow conflict.
3661        let _msgbus = get_message_bus();
3662        let command_sent = Rc::new(RefCell::new(false));
3663        let command_sent_clone = command_sent.clone();
3664
3665        let cmd_received = Rc::new(RefCell::new(false));
3666        let cmd_received_clone = cmd_received.clone();
3667        let cmd_handler = TypedIntoHandler::from(move |_cmd: DataCommand| {
3668            *cmd_received_clone.borrow_mut() = true;
3669        });
3670        let cmd_endpoint: MStr<Endpoint> = "ReentrantTest.dataCmd2".into();
3671        register_data_command_endpoint(cmd_endpoint, cmd_handler);
3672
3673        let data_handler = TypedIntoHandler::from(move |_data: Data| {
3674            let command = DataCommand::Subscribe(SubscribeCommand::Quotes(SubscribeQuotes::new(
3675                InstrumentId::from("TEST.VENUE"),
3676                Some(ClientId::new("SIM")),
3677                None,
3678                UUID4::new(),
3679                0.into(),
3680                None,
3681                None,
3682            )));
3683            send_data_command(cmd_endpoint, command);
3684            *command_sent_clone.borrow_mut() = true;
3685        });
3686
3687        let data_endpoint: MStr<Endpoint> = "ReentrantTest.data2".into();
3688        register_data_endpoint(data_endpoint, data_handler);
3689
3690        let quote = QuoteTick::default();
3691        send_data(data_endpoint, Data::Quote(quote));
3692
3693        assert!(*command_sent.borrow(), "Data handler should have run");
3694        assert!(
3695            *cmd_received.borrow(),
3696            "Data command should have been received"
3697        );
3698    }
3699
3700    #[rstest]
3701    fn test_trading_command_handler_can_send_order_event() {
3702        // Tests that a handler processing a trading command can send an order event
3703        // without causing a borrow conflict. This is the reverse direction of the
3704        // common re-entrancy scenario.
3705        let _msgbus = get_message_bus();
3706        let event_sent = Rc::new(RefCell::new(false));
3707        let event_sent_clone = event_sent.clone();
3708
3709        let evt_received = Rc::new(RefCell::new(false));
3710        let evt_received_clone = evt_received.clone();
3711        let evt_handler = TypedIntoHandler::from(move |_event: OrderEventAny| {
3712            *evt_received_clone.borrow_mut() = true;
3713        });
3714        let evt_endpoint: MStr<Endpoint> = "ReentrantTest.orderEvt2".into();
3715        register_order_event_endpoint(evt_endpoint, evt_handler);
3716
3717        let cmd_handler = TypedIntoHandler::from(move |_cmd: TradingCommand| {
3718            let event = OrderEventAny::Denied(OrderDeniedSpec::builder().build());
3719            send_order_event(evt_endpoint, event);
3720            *event_sent_clone.borrow_mut() = true;
3721        });
3722
3723        let cmd_endpoint: MStr<Endpoint> = "ReentrantTest.execCmd2".into();
3724        register_trading_command_endpoint(cmd_endpoint, cmd_handler);
3725
3726        let command = TradingCommand::CancelAllOrders(CancelAllOrders::new(
3727            TraderId::new("TESTER-001"),
3728            None,
3729            StrategyId::new("S-001"),
3730            InstrumentId::from("TEST.VENUE"),
3731            Some(OrderSide::Buy),
3732            UUID4::new(),
3733            0.into(),
3734            None,
3735            None, // correlation_id
3736        ));
3737        send_trading_command(cmd_endpoint, command);
3738
3739        assert!(
3740            *event_sent.borrow(),
3741            "Trading command handler should have run"
3742        );
3743        assert!(
3744            *evt_received.borrow(),
3745            "Order event should have been received"
3746        );
3747    }
3748
3749    #[rstest]
3750    fn test_nested_reentrant_calls() {
3751        // Tests deeply nested re-entrant calls: order event -> trading command -> order event.
3752        // This simulates a complex scenario where handlers chain multiple calls.
3753        let _msgbus = get_message_bus();
3754        let call_depth = Rc::new(RefCell::new(0u32));
3755
3756        let final_received = Rc::new(RefCell::new(false));
3757        let final_received_clone = final_received.clone();
3758        let final_evt_handler = TypedIntoHandler::from(move |_event: OrderEventAny| {
3759            *final_received_clone.borrow_mut() = true;
3760        });
3761        let final_evt_endpoint: MStr<Endpoint> = "ReentrantTest.finalEvt".into();
3762        register_order_event_endpoint(final_evt_endpoint, final_evt_handler);
3763
3764        let call_depth_clone2 = call_depth.clone();
3765        let mid_cmd_handler = TypedIntoHandler::from(move |_cmd: TradingCommand| {
3766            *call_depth_clone2.borrow_mut() += 1;
3767            let event = OrderEventAny::Denied(OrderDeniedSpec::builder().build());
3768            send_order_event(final_evt_endpoint, event);
3769        });
3770        let mid_cmd_endpoint: MStr<Endpoint> = "ReentrantTest.midCmd".into();
3771        register_trading_command_endpoint(mid_cmd_endpoint, mid_cmd_handler);
3772
3773        let call_depth_clone1 = call_depth.clone();
3774        let init_evt_handler = TypedIntoHandler::from(move |_event: OrderEventAny| {
3775            *call_depth_clone1.borrow_mut() += 1;
3776            let command = TradingCommand::CancelAllOrders(CancelAllOrders::new(
3777                TraderId::new("TESTER-001"),
3778                None,
3779                StrategyId::new("S-001"),
3780                InstrumentId::from("TEST.VENUE"),
3781                Some(OrderSide::Buy),
3782                UUID4::new(),
3783                0.into(),
3784                None,
3785                None, // correlation_id
3786            ));
3787            send_trading_command(mid_cmd_endpoint, command);
3788        });
3789        let init_evt_endpoint: MStr<Endpoint> = "ReentrantTest.initEvt".into();
3790        register_order_event_endpoint(init_evt_endpoint, init_evt_handler);
3791
3792        let event = OrderEventAny::Denied(OrderDeniedSpec::builder().build());
3793        send_order_event(init_evt_endpoint, event);
3794
3795        assert_eq!(
3796            *call_depth.borrow(),
3797            2,
3798            "Both intermediate handlers should have run"
3799        );
3800        assert!(
3801            *final_received.borrow(),
3802            "Final event handler should have received the event"
3803        );
3804    }
3805
3806    /// Recording tap used by the bus-tap registration tests. Stores every dispatched
3807    /// topic / endpoint plus a digest of the message so a test can assert the tap
3808    /// observed the exact dispatches it expected.
3809    #[derive(Default)]
3810    struct RecordingTap {
3811        publishes: RefCell<Vec<(String, std::any::TypeId)>>,
3812        sends: RefCell<Vec<(String, std::any::TypeId)>>,
3813        responses: RefCell<Vec<(UUID4, std::any::TypeId)>>,
3814    }
3815
3816    impl RecordingTap {
3817        fn publish_topics(&self) -> Vec<String> {
3818            self.publishes
3819                .borrow()
3820                .iter()
3821                .map(|(t, _)| t.clone())
3822                .collect()
3823        }
3824
3825        fn send_endpoints(&self) -> Vec<String> {
3826            self.sends.borrow().iter().map(|(e, _)| e.clone()).collect()
3827        }
3828
3829        fn response_correlation_ids(&self) -> Vec<UUID4> {
3830            self.responses.borrow().iter().map(|(id, _)| *id).collect()
3831        }
3832    }
3833
3834    impl BusTap for RecordingTap {
3835        fn on_publish(&self, topic: MStr<Topic>, message: &dyn std::any::Any) {
3836            self.publishes
3837                .borrow_mut()
3838                .push((topic.to_string(), message.type_id()));
3839        }
3840
3841        fn on_send(&self, endpoint: MStr<Endpoint>, message: &dyn std::any::Any) {
3842            self.sends
3843                .borrow_mut()
3844                .push((endpoint.to_string(), message.type_id()));
3845        }
3846
3847        fn on_response(&self, correlation_id: &UUID4, message: &dyn std::any::Any) {
3848            self.responses
3849                .borrow_mut()
3850                .push((*correlation_id, message.type_id()));
3851        }
3852    }
3853
3854    #[rstest]
3855    fn try_publish_any_dispatches_handler_and_tap() {
3856        let msgbus = Rc::new(RefCell::new(MessageBus::default()));
3857        set_message_bus(msgbus.clone());
3858        clear_bus_tap();
3859
3860        let tap = Rc::new(RecordingTap::default());
3861        set_bus_tap(tap.clone());
3862
3863        let topic = "data.any.try.test";
3864        let (handler, checker) = get_call_check_handler(None);
3865        let pub_count = msgbus.borrow().pub_count();
3866        subscribe_any(topic.into(), handler, None);
3867
3868        let payload: u32 = 42;
3869        let published = try_publish_any(topic.into(), &payload);
3870
3871        clear_bus_tap();
3872
3873        assert!(published);
3874        assert!(checker.was_called());
3875        assert_eq!(msgbus.borrow().pub_count(), pub_count + 1);
3876        assert_eq!(tap.publish_topics(), vec![topic]);
3877    }
3878
3879    #[rstest]
3880    fn try_publish_any_without_registered_bus_returns_false() {
3881        let published = thread::spawn(|| {
3882            let payload: u32 = 42;
3883            try_publish_any("data.any.no-bus.test".into(), &payload)
3884        })
3885        .join()
3886        .expect("thread should join");
3887
3888        assert!(!published);
3889    }
3890
3891    #[rstest]
3892    fn try_publish_any_with_borrowed_bus_returns_false_without_tap() {
3893        let msgbus = Rc::new(RefCell::new(MessageBus::default()));
3894        set_message_bus(msgbus.clone());
3895        clear_bus_tap();
3896
3897        let tap = Rc::new(RecordingTap::default());
3898        set_bus_tap(tap.clone());
3899
3900        let bus_borrow = msgbus.borrow_mut();
3901        let payload: u32 = 42;
3902        let published = try_publish_any("data.any.borrowed.test".into(), &payload);
3903        drop(bus_borrow);
3904
3905        clear_bus_tap();
3906
3907        assert!(!published);
3908        assert_eq!(msgbus.borrow().pub_count(), 0);
3909        assert!(tap.publish_topics().is_empty());
3910    }
3911
3912    #[rstest]
3913    fn set_bus_tap_then_publish_typed_invokes_tap() {
3914        let _msgbus = get_message_bus();
3915        let tap = Rc::new(RecordingTap::default());
3916        set_bus_tap(tap.clone());
3917
3918        let quote = QuoteTick::default();
3919        publish_quote("data.quotes.tap.test".into(), &quote);
3920
3921        clear_bus_tap();
3922
3923        assert_eq!(tap.publish_topics(), vec!["data.quotes.tap.test"]);
3924    }
3925
3926    #[rstest]
3927    fn set_bus_tap_then_publish_any_invokes_tap() {
3928        let _msgbus = get_message_bus();
3929        let tap = Rc::new(RecordingTap::default());
3930        set_bus_tap(tap.clone());
3931
3932        let payload: u32 = 42;
3933        publish_any("data.any.tap.test".into(), &payload);
3934
3935        clear_bus_tap();
3936
3937        assert_eq!(tap.publish_topics(), vec!["data.any.tap.test"]);
3938    }
3939
3940    #[rstest]
3941    fn set_bus_tap_then_send_any_value_invokes_tap() {
3942        let _msgbus = get_message_bus();
3943        let tap = Rc::new(RecordingTap::default());
3944        set_bus_tap(tap.clone());
3945
3946        let payload: u32 = 7;
3947        send_any_value("endpoint.send.any.value.test".into(), &payload);
3948
3949        clear_bus_tap();
3950
3951        assert_eq!(tap.send_endpoints(), vec!["endpoint.send.any.value.test"],);
3952    }
3953
3954    #[rstest]
3955    fn send_any_variants_update_tap_and_count_before_handler() {
3956        let msgbus = Rc::new(RefCell::new(MessageBus::default()));
3957        set_message_bus(msgbus.clone());
3958        clear_bus_tap();
3959
3960        let endpoint: MStr<Endpoint> = "endpoint.send.any.order.test".into();
3961        let tap = Rc::new(RecordingTap::default());
3962        set_bus_tap(tap.clone());
3963
3964        let observations = Rc::new(RefCell::new(Vec::new()));
3965        let observations_clone = observations.clone();
3966        let handler = ShareableMessageHandler::from_any(move |message| {
3967            let value = *message.downcast_ref::<u32>().unwrap();
3968            observations_clone.borrow_mut().push((
3969                value,
3970                tap.send_endpoints(),
3971                get_message_bus().borrow().sent_count(),
3972            ));
3973        });
3974        register_any(endpoint, handler);
3975
3976        send_any(endpoint, &11_u32);
3977        send_any_value(endpoint, &22_u32);
3978
3979        clear_bus_tap();
3980
3981        assert_eq!(
3982            *observations.borrow(),
3983            vec![
3984                (11, vec![endpoint.to_string()], 1),
3985                (22, vec![endpoint.to_string(), endpoint.to_string()], 2),
3986            ]
3987        );
3988        assert_eq!(msgbus.borrow().sent_count(), 2);
3989    }
3990
3991    #[rstest]
3992    fn set_bus_tap_then_send_endpoint_owned_invokes_tap() {
3993        // send_trading_command (and the other owned send helpers) reach the tap
3994        // through send_endpoint_owned_counted. Without this site instrumented, real
3995        // production order commands would bypass the audit log.
3996        let _msgbus = get_message_bus();
3997        let tap = Rc::new(RecordingTap::default());
3998        set_bus_tap(tap.clone());
3999
4000        let cancel_all = CancelAllOrders::new(
4001            TraderId::from("TRADER-001"),
4002            Some(ClientId::from("BINANCE")),
4003            StrategyId::from("S-001"),
4004            InstrumentId::from("ETHUSDT-PERP.BINANCE"),
4005            Some(OrderSide::Buy),
4006            UUID4::new(),
4007            nautilus_core::UnixNanos::from(1),
4008            None,
4009            None, // correlation_id
4010        );
4011        send_trading_command(
4012            "endpoint.send.trading.command.test".into(),
4013            TradingCommand::CancelAllOrders(cancel_all),
4014        );
4015
4016        clear_bus_tap();
4017
4018        assert_eq!(
4019            tap.send_endpoints(),
4020            vec!["endpoint.send.trading.command.test"],
4021        );
4022    }
4023
4024    #[rstest]
4025    fn set_bus_tap_then_send_endpoint_ref_invokes_tap() {
4026        // send_quote (and the other typed-ref send helpers) reach the tap through
4027        // send_endpoint_ref. Mirrors the owned path coverage.
4028        let _msgbus = get_message_bus();
4029        let tap = Rc::new(RecordingTap::default());
4030        set_bus_tap(tap.clone());
4031
4032        let quote = QuoteTick::default();
4033        send_quote("endpoint.send.quote.test".into(), &quote);
4034
4035        clear_bus_tap();
4036
4037        assert_eq!(tap.send_endpoints(), vec!["endpoint.send.quote.test"]);
4038    }
4039
4040    #[rstest]
4041    fn has_quote_endpoint_returns_registration_state() {
4042        let _msgbus = get_message_bus();
4043        let endpoint: MStr<Endpoint> = "endpoint.has.quote.registered".into();
4044
4045        assert!(!has_quote_endpoint(endpoint));
4046
4047        let handler = TypedHandler::from_with_id(endpoint, |_quote: &QuoteTick| {});
4048        register_quote_endpoint(endpoint, handler);
4049
4050        assert!(has_quote_endpoint(endpoint));
4051    }
4052
4053    #[rstest]
4054    fn set_bus_tap_then_send_response_invokes_tap() {
4055        reset_message_bus();
4056        clear_bus_tap();
4057        let msgbus = get_message_bus();
4058        let initial_response_count = msgbus.borrow().res_count();
4059        let tap = Rc::new(RecordingTap::default());
4060        set_bus_tap(tap.clone());
4061
4062        let correlation_id = UUID4::new();
4063        let handler_calls = Rc::new(Cell::new(0));
4064        let handler_calls_clone = handler_calls.clone();
4065        register_response_handler(
4066            &correlation_id,
4067            ShareableMessageHandler::from_typed(move |_resp: &QuotesResponse| {
4068                handler_calls_clone.set(handler_calls_clone.get() + 1);
4069            }),
4070        );
4071
4072        let response = DataResponse::Quotes(QuotesResponse {
4073            correlation_id,
4074            client_id: ClientId::new("SIM"),
4075            instrument_id: InstrumentId::from("TEST.VENUE"),
4076            data: vec![],
4077            start: None,
4078            end: None,
4079            ts_init: 0.into(),
4080            params: None,
4081        });
4082        send_response(&correlation_id, &response);
4083        send_response(&correlation_id, &response);
4084
4085        clear_bus_tap();
4086
4087        assert_eq!(handler_calls.get(), 1);
4088        assert_eq!(
4089            tap.response_correlation_ids(),
4090            vec![correlation_id, correlation_id],
4091        );
4092        assert_eq!(msgbus.borrow().res_count(), initial_response_count + 2);
4093        assert!(
4094            msgbus
4095                .borrow()
4096                .get_response_handler(&correlation_id)
4097                .is_none(),
4098        );
4099    }
4100
4101    #[rstest]
4102    fn clear_bus_tap_prevents_subsequent_dispatches_from_invoking_tap() {
4103        let _msgbus = get_message_bus();
4104        let tap = Rc::new(RecordingTap::default());
4105        set_bus_tap(tap.clone());
4106        clear_bus_tap();
4107
4108        let quote = QuoteTick::default();
4109        publish_quote("data.quotes.after.clear".into(), &quote);
4110        send_quote("endpoint.send.after.clear".into(), &quote);
4111
4112        let correlation_id = UUID4::new();
4113        register_response_handler(
4114            &correlation_id,
4115            ShareableMessageHandler::from_typed(|_resp: &QuotesResponse| {}),
4116        );
4117        let response = DataResponse::Quotes(QuotesResponse {
4118            correlation_id,
4119            client_id: ClientId::new("SIM"),
4120            instrument_id: InstrumentId::from("TEST.VENUE"),
4121            data: vec![],
4122            start: None,
4123            end: None,
4124            ts_init: 0.into(),
4125            params: None,
4126        });
4127        send_response(&correlation_id, &response);
4128
4129        assert!(tap.publish_topics().is_empty());
4130        assert!(tap.send_endpoints().is_empty());
4131        assert!(tap.response_correlation_ids().is_empty());
4132    }
4133
4134    #[rstest]
4135    fn dispatch_with_no_tap_installed_is_a_noop() {
4136        // A fresh thread starts with BUS_TAP=None; dispatches must not panic and must
4137        // not allocate any tap state. Sanity check that the Option::None branch in
4138        // dispatch_tap_* is hit cleanly.
4139        let _msgbus = get_message_bus();
4140
4141        let quote = QuoteTick::default();
4142        publish_quote("data.quotes.no.tap".into(), &quote);
4143        send_quote("endpoint.no.tap".into(), &quote);
4144    }
4145
4146    struct ReinstallTap;
4147
4148    impl BusTap for ReinstallTap {
4149        fn on_publish(&self, _topic: MStr<Topic>, _message: &dyn std::any::Any) {
4150            // Replace ourselves mid-dispatch; must not deadlock the RefCell
4151            set_bus_tap(Rc::new(NoopTap));
4152        }
4153
4154        fn on_send(&self, _endpoint: MStr<Endpoint>, _message: &dyn std::any::Any) {}
4155    }
4156
4157    struct NoopTap;
4158
4159    impl BusTap for NoopTap {
4160        fn on_publish(&self, _topic: MStr<Topic>, _message: &dyn std::any::Any) {}
4161        fn on_send(&self, _endpoint: MStr<Endpoint>, _message: &dyn std::any::Any) {}
4162    }
4163
4164    #[rstest]
4165    fn reentrant_set_bus_tap_during_dispatch_does_not_panic() {
4166        // dispatch_tap_publish clones the Rc out of BUS_TAP before invoking on_publish,
4167        // so a tap whose on_publish reinstalls a different tap must not panic on the
4168        // cell. The replaced tap stays alive through the cloned Rc until the dispatch
4169        // returns.
4170        let _msgbus = get_message_bus();
4171        set_bus_tap(Rc::new(ReinstallTap));
4172
4173        let quote = QuoteTick::default();
4174        publish_quote("data.quotes.reentrant".into(), &quote);
4175
4176        clear_bus_tap();
4177    }
4178}