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