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