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