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