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