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