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