1use std::{any::Any, cell::Cell};
19
20use anyhow::Context;
21use nautilus_model::{
22 data::{CustomData, Data, deserialize_custom_from_json},
23 reports::{ExecutionMassStatus, FillReport, OrderStatusReport, PositionStatusReport},
24};
25use serde::de::DeserializeOwned;
26use ustr::Ustr;
27
28pub(crate) mod codec;
29
30use self::codec::PayloadCodecError;
31use super::{
32 BusMessage, BusMessageSource, BusPayloadType, HAS_EXTERNAL_EGRESS, MessagingSwitchboard,
33 SUPPRESS_EXTERNAL_DEPTH, SuppressExternalGuard,
34 api::{
35 publish_any, publish_bar, publish_deltas, publish_depth, publish_funding_rate,
36 publish_index_price, publish_instrument, publish_mark_price, publish_option_greeks,
37 publish_order_event, publish_portfolio_snapshot, publish_position_event, publish_quote,
38 publish_trade, send_account_state,
39 },
40 get_message_bus,
41 mstr::{MStr, Topic},
42};
43use crate::{
44 enums::SerializationEncoding,
45 messages::{
46 data::{
47 DataResponse, DataResponseEnvelope, RequestCommand, SubscribeCommand,
48 UnsubscribeCommand,
49 },
50 execution::{GenerateExecutionMassStatus, TradingCommand},
51 },
52};
53
54#[inline(always)]
55pub(super) fn forward_to_external_egress<T>(
56 topic: MStr<Topic>,
57 payload_type: BusPayloadType,
58 message: &T,
59) where
60 T: serde::Serialize + Any,
61{
62 if !HAS_EXTERNAL_EGRESS.with(Cell::get) {
63 return;
64 }
65
66 forward_external_message(topic, payload_type, message);
67}
68
69#[inline]
70pub(super) fn forward_any_to_external_egress(topic: MStr<Topic>, message: &dyn Any) {
71 if !HAS_EXTERNAL_EGRESS.with(Cell::get) {
72 return;
73 }
74
75 if let Some(custom) = message.downcast_ref::<CustomData>() {
76 forward_external_message(
77 topic,
78 BusPayloadType::Custom(Ustr::from(custom.data.type_name())),
79 custom,
80 );
81 return;
82 }
83
84 if forward_downcast::<SubscribeCommand>(topic, BusPayloadType::SubscribeCommand, message)
85 || forward_downcast::<UnsubscribeCommand>(
86 topic,
87 BusPayloadType::UnsubscribeCommand,
88 message,
89 )
90 || forward_downcast::<DataResponse>(topic, BusPayloadType::DataResponse, message)
91 || forward_downcast::<DataResponseEnvelope>(
92 topic,
93 BusPayloadType::DataResponseEnvelope,
94 message,
95 )
96 || forward_downcast::<RequestCommand>(topic, BusPayloadType::RequestCommand, message)
97 || forward_downcast::<TradingCommand>(topic, BusPayloadType::TradingCommand, message)
98 || forward_downcast::<GenerateExecutionMassStatus>(
99 topic,
100 BusPayloadType::GenerateExecutionMassStatus,
101 message,
102 )
103 || forward_downcast::<OrderStatusReport>(topic, BusPayloadType::OrderStatusReport, message)
104 || forward_downcast::<FillReport>(topic, BusPayloadType::FillReport, message)
105 || forward_downcast::<PositionStatusReport>(
106 topic,
107 BusPayloadType::PositionStatusReport,
108 message,
109 )
110 {
111 return;
112 }
113
114 forward_downcast::<ExecutionMassStatus>(topic, BusPayloadType::ExecutionMassStatus, message);
115}
116
117fn forward_downcast<T>(topic: MStr<Topic>, payload_type: BusPayloadType, message: &dyn Any) -> bool
118where
119 T: serde::Serialize + Any,
120{
121 let Some(message) = message.downcast_ref::<T>() else {
122 return false;
123 };
124
125 forward_external_message(topic, payload_type, message);
126 true
127}
128
129#[cold]
130#[inline(never)]
131fn forward_external_message<T>(topic: MStr<Topic>, payload_type: BusPayloadType, message: &T)
132where
133 T: serde::Serialize + Any,
134{
135 if SUPPRESS_EXTERNAL_DEPTH.with(Cell::get) > 0 {
136 return;
137 }
138
139 let (external_egress, encoding) = {
140 let bus_rc = get_message_bus();
141 let bus = bus_rc.borrow();
142
143 let Some(external_egress) = bus.external_egress() else {
144 return;
145 };
146
147 if payload_type.is_typed_message() && !bus.has_external_streams() {
148 return;
149 }
150
151 if bus.types_filter().contains(&payload_type) {
152 return;
153 }
154
155 (external_egress, bus.encoding_for(payload_type))
156 };
157
158 if external_egress.borrow().is_closed() {
159 return;
160 }
161
162 let payload = match codec::serialize_payload(encoding, payload_type, message) {
163 Ok(payload) => payload,
164 Err(PayloadCodecError::Dropped(e)) => {
165 log::debug!("{e}");
166 return;
167 }
168 Err(PayloadCodecError::Failed(e)) => {
169 log::error!("{e}");
170 return;
171 }
172 };
173
174 let external_egress = external_egress.borrow();
175 if external_egress.is_closed() {
176 return;
177 }
178
179 external_egress.publish(BusMessage::new(*topic, payload_type, payload, encoding));
181}
182
183thread_local! {
184 static EXTERNAL_SOURCE: Cell<Option<BusMessageSource>> = const { Cell::new(None) };
185}
186
187#[must_use]
189pub fn current_external_source() -> Option<BusMessageSource> {
190 EXTERNAL_SOURCE.get()
191}
192
193struct ExternalSourceGuard(Option<BusMessageSource>);
194
195impl ExternalSourceGuard {
196 fn new(source: Option<BusMessageSource>) -> Self {
197 Self(EXTERNAL_SOURCE.replace(source))
198 }
199}
200
201impl Drop for ExternalSourceGuard {
202 fn drop(&mut self) {
203 EXTERNAL_SOURCE.set(self.0);
204 }
205}
206
207pub fn republish_pending_external_response(message: &BusMessage) -> anyhow::Result<bool> {
220 if !matches!(
221 message.payload_type,
222 BusPayloadType::DataResponse | BusPayloadType::DataResponseEnvelope
223 ) || !is_registered_streaming_type(message)
224 {
225 return Ok(false);
226 }
227
228 match message.payload_type {
229 BusPayloadType::DataResponseEnvelope => republish_pending_response::<DataResponseEnvelope>(
230 message,
231 DataResponseEnvelope::correlation_id,
232 ),
233 _ => republish_pending_response::<DataResponse>(message, DataResponse::correlation_id),
234 }
235}
236
237fn republish_pending_response<T: serde::de::DeserializeOwned + 'static>(
238 message: &BusMessage,
239 correlation_id: fn(&T) -> &nautilus_core::UUID4,
240) -> anyhow::Result<bool> {
241 let Some(response) =
242 codec::deserialize_payload::<T>(message.payload_type, message.encoding, &message.payload)?
243 else {
244 return Ok(false);
245 };
246
247 if get_message_bus()
248 .borrow()
249 .get_response_handler(correlation_id(&response))
250 .is_none()
251 {
252 return Ok(false);
253 }
254
255 let topic =
256 MStr::<Topic>::topic_from_ustr(message.topic).context("invalid external message topic")?;
257 let _guard = SuppressExternalGuard::new();
258 let _source = ExternalSourceGuard::new(message.source);
259 publish_any(topic, &response);
260 Ok(true)
261}
262
263pub fn republish_external_message(message: &BusMessage) -> anyhow::Result<()> {
280 let topic =
281 MStr::<Topic>::topic_from_ustr(message.topic).context("invalid external message topic")?;
282
283 if !is_registered_streaming_type(message) {
284 return Ok(());
285 }
286
287 let _guard = SuppressExternalGuard::new();
288 let _source = ExternalSourceGuard::new(message.source);
289
290 match message.payload_type {
291 BusPayloadType::Custom(_) => {
292 handle_custom_data(
293 topic,
294 message.payload_type,
295 message.encoding,
296 &message.payload,
297 )?;
298 }
299 BusPayloadType::Instrument => {
300 handle_payload(
301 topic,
302 message.payload_type,
303 message.encoding,
304 &message.payload,
305 publish_instrument,
306 )?;
307 }
308 BusPayloadType::OrderBookDeltas => handle_market_data(
309 topic,
310 message.encoding,
311 &message.payload,
312 codec::deserialize_order_book_deltas,
313 publish_deltas,
314 )?,
315 BusPayloadType::OrderBookDepth => handle_market_data(
316 topic,
317 message.encoding,
318 &message.payload,
319 codec::deserialize_order_book_depth,
320 publish_depth,
321 )?,
322 BusPayloadType::QuoteTick => handle_market_data(
323 topic,
324 message.encoding,
325 &message.payload,
326 codec::deserialize_quote,
327 publish_quote,
328 )?,
329 BusPayloadType::TradeTick => handle_market_data(
330 topic,
331 message.encoding,
332 &message.payload,
333 codec::deserialize_trade,
334 publish_trade,
335 )?,
336 BusPayloadType::Bar => handle_market_data(
337 topic,
338 message.encoding,
339 &message.payload,
340 codec::deserialize_bar,
341 publish_bar,
342 )?,
343 BusPayloadType::MarkPriceUpdate => handle_market_data(
344 topic,
345 message.encoding,
346 &message.payload,
347 codec::deserialize_mark_price,
348 publish_mark_price,
349 )?,
350 BusPayloadType::IndexPriceUpdate => handle_market_data(
351 topic,
352 message.encoding,
353 &message.payload,
354 codec::deserialize_index_price,
355 publish_index_price,
356 )?,
357 BusPayloadType::FundingRateUpdate => handle_market_data(
358 topic,
359 message.encoding,
360 &message.payload,
361 codec::deserialize_funding_rate,
362 publish_funding_rate,
363 )?,
364 BusPayloadType::OptionGreeks => {
365 handle_market_data(
366 topic,
367 message.encoding,
368 &message.payload,
369 codec::deserialize_option_greeks,
370 publish_option_greeks,
371 )?;
372 }
373 BusPayloadType::AccountState => {
374 handle_payload(
375 topic,
376 message.payload_type,
377 message.encoding,
378 &message.payload,
379 |_, state| {
380 send_account_state(MessagingSwitchboard::portfolio_update_account(), state);
381 },
382 )?;
383 }
384 BusPayloadType::OrderEvent => {
385 handle_payload(
386 topic,
387 message.payload_type,
388 message.encoding,
389 &message.payload,
390 publish_order_event,
391 )?;
392 }
393 BusPayloadType::PositionEvent => {
394 handle_payload(
395 topic,
396 message.payload_type,
397 message.encoding,
398 &message.payload,
399 publish_position_event,
400 )?;
401 }
402 BusPayloadType::PortfolioSnapshot => {
403 handle_payload(
404 topic,
405 message.payload_type,
406 message.encoding,
407 &message.payload,
408 publish_portfolio_snapshot,
409 )?;
410 }
411 BusPayloadType::SubscribeCommand => {
412 handle_payload_any::<SubscribeCommand>(topic, message)?;
413 }
414 BusPayloadType::UnsubscribeCommand => {
415 handle_payload_any::<UnsubscribeCommand>(topic, message)?;
416 }
417 BusPayloadType::DataResponse => {
418 handle_payload_any::<DataResponse>(topic, message)?;
419 }
420 BusPayloadType::DataResponseEnvelope => {
421 handle_payload_any::<DataResponseEnvelope>(topic, message)?;
422 }
423 BusPayloadType::RequestCommand => {
424 handle_payload_any::<RequestCommand>(topic, message)?;
425 }
426 BusPayloadType::TradingCommand => {
427 handle_payload_any::<TradingCommand>(topic, message)?;
428 }
429 BusPayloadType::GenerateExecutionMassStatus => {
430 handle_payload_any::<GenerateExecutionMassStatus>(topic, message)?;
431 }
432 BusPayloadType::OrderStatusReport => {
433 handle_payload_any::<OrderStatusReport>(topic, message)?;
434 }
435 BusPayloadType::FillReport => {
436 handle_payload_any::<FillReport>(topic, message)?;
437 }
438 BusPayloadType::PositionStatusReport => {
439 handle_payload_any::<PositionStatusReport>(topic, message)?;
440 }
441 BusPayloadType::ExecutionMassStatus => {
442 handle_payload_any::<ExecutionMassStatus>(topic, message)?;
443 }
444 #[cfg(feature = "defi")]
445 BusPayloadType::Block
446 | BusPayloadType::Pool
447 | BusPayloadType::PoolLiquidityUpdate
448 | BusPayloadType::PoolFeeCollect
449 | BusPayloadType::PoolFlash => {
450 crate::defi::msgbus::republish_external_message(
451 topic,
452 message.payload_type,
453 message.encoding,
454 &message.payload,
455 )?;
456 }
457 }
458
459 Ok(())
460}
461
462pub fn process_external_typed_message(
484 message: &BusMessage,
485 processor: &mut dyn FnMut(&dyn Any, &serde_json::Value) -> anyhow::Result<()>,
486) -> anyhow::Result<()> {
487 let topic =
488 MStr::<Topic>::topic_from_ustr(message.topic).context("invalid external message topic")?;
489 let _guard = SuppressExternalGuard::new();
490 let _source = ExternalSourceGuard::new(message.source);
491
492 #[rustfmt::skip]
493 let result = match message.payload_type {
494 BusPayloadType::SubscribeCommand => process_typed_payload::<SubscribeCommand>(topic, message, processor),
495 BusPayloadType::UnsubscribeCommand => process_typed_payload::<UnsubscribeCommand>(topic, message, processor),
496 BusPayloadType::DataResponse => process_typed_payload::<DataResponse>(topic, message, processor),
497 BusPayloadType::DataResponseEnvelope => process_typed_payload::<DataResponseEnvelope>(topic, message, processor),
498 BusPayloadType::RequestCommand => process_typed_payload::<RequestCommand>(topic, message, processor),
499 BusPayloadType::TradingCommand => process_typed_payload::<TradingCommand>(topic, message, processor),
500 BusPayloadType::GenerateExecutionMassStatus => process_typed_payload::<GenerateExecutionMassStatus>(topic, message, processor),
501 BusPayloadType::OrderStatusReport => process_typed_payload::<OrderStatusReport>(topic, message, processor),
502 BusPayloadType::FillReport => process_typed_payload::<FillReport>(topic, message, processor),
503 BusPayloadType::PositionStatusReport => process_typed_payload::<PositionStatusReport>(topic, message, processor),
504 BusPayloadType::ExecutionMassStatus => process_typed_payload::<ExecutionMassStatus>(topic, message, processor),
505 _ => republish_external_message(message),
506 };
507 result
508}
509
510fn handle_payload_any<T>(topic: MStr<Topic>, message: &BusMessage) -> anyhow::Result<()>
511where
512 T: DeserializeOwned + Any,
513{
514 handle_payload(
515 topic,
516 message.payload_type,
517 message.encoding,
518 &message.payload,
519 |topic, value: &T| publish_any(topic, value),
520 )
521}
522
523fn process_typed_payload<T>(
524 topic: MStr<Topic>,
525 message: &BusMessage,
526 processor: &mut dyn FnMut(&dyn Any, &serde_json::Value) -> anyhow::Result<()>,
527) -> anyhow::Result<()>
528where
529 T: DeserializeOwned + serde::Serialize + Any,
530{
531 let Some(value) =
532 codec::deserialize_payload::<T>(message.payload_type, message.encoding, &message.payload)?
533 else {
534 return Ok(());
535 };
536
537 let mut mapping = serde_json::to_value(&value).with_context(|| {
538 format!(
539 "failed to map decoded {} stream payload",
540 message.payload_type
541 )
542 })?;
543
544 let mapping_object = mapping.as_object_mut().with_context(|| {
545 format!(
546 "decoded {} stream payload did not map to an object",
547 message.payload_type
548 )
549 })?;
550
551 anyhow::ensure!(
552 !mapping_object.contains_key("payload_type"),
553 "decoded {} stream payload contains reserved payload_type field",
554 message.payload_type
555 );
556 mapping_object.insert(
557 "payload_type".to_string(),
558 serde_json::Value::String(message.payload_type.as_str().to_string()),
559 );
560
561 processor(&value, &mapping)?;
562 if is_registered_streaming_type(message) {
563 publish_any(topic, &value);
564 }
565
566 Ok(())
567}
568
569fn is_registered_streaming_type(message: &BusMessage) -> bool {
570 if get_message_bus()
571 .borrow()
572 .is_streaming_type(message.payload_type)
573 {
574 return true;
575 }
576
577 let type_name = message.payload_type.as_str();
578 if type_name.is_empty() {
579 log::debug!(
580 "Skipping external message on topic '{}' with no payload type for inbound republishing",
581 message.topic
582 );
583 } else {
584 log::debug!(
585 "Skipping external {type_name} message on topic '{}' because the type is not registered for streaming",
586 message.topic
587 );
588 }
589
590 false
591}
592
593pub(crate) fn handle_payload<T>(
594 topic: MStr<Topic>,
595 payload_type: BusPayloadType,
596 encoding: SerializationEncoding,
597 payload: &[u8],
598 publish: impl FnOnce(MStr<Topic>, &T),
599) -> anyhow::Result<()>
600where
601 T: DeserializeOwned + 'static,
602{
603 let Some(value) = codec::deserialize_payload(payload_type, encoding, payload)? else {
604 return Ok(());
605 };
606
607 publish(topic, &value);
608 Ok(())
609}
610
611fn handle_market_data<T>(
612 topic: MStr<Topic>,
613 encoding: SerializationEncoding,
614 payload: &[u8],
615 deserialize: fn(SerializationEncoding, &[u8]) -> anyhow::Result<Option<T>>,
616 publish: impl FnOnce(MStr<Topic>, &T),
617) -> anyhow::Result<()> {
618 let Some(value) = deserialize(encoding, payload)? else {
619 return Ok(());
620 };
621
622 publish(topic, &value);
623 Ok(())
624}
625
626fn handle_custom_data(
627 topic: MStr<Topic>,
628 payload_type: BusPayloadType,
629 encoding: SerializationEncoding,
630 payload: &[u8],
631) -> anyhow::Result<()> {
632 let Some(custom) = decode_custom_data_payload(payload_type, encoding, payload)? else {
633 return Ok(());
634 };
635
636 publish_any(topic, &custom);
637 Ok(())
638}
639
640fn decode_custom_data_payload(
641 payload_type: BusPayloadType,
642 encoding: SerializationEncoding,
643 payload: &[u8],
644) -> anyhow::Result<Option<CustomData>> {
645 let BusPayloadType::Custom(custom_type_name) = payload_type else {
646 unreachable!("custom data payload decoding requires a custom payload type");
647 };
648
649 if custom_type_name.is_empty() {
650 log::warn!("External payload has no type for inbound republishing");
651 return Ok(None);
652 } else if !payload_type.supports(encoding) {
653 codec::warn_unsupported_inbound(payload_type, encoding);
654 return Ok(None);
655 }
656
657 match encoding {
658 SerializationEncoding::Json => {
659 let value =
660 codec::deserialize_json_payload::<serde_json::Value>(payload, "CustomData")?;
661 decode_custom_data_value(custom_type_name, &value)
662 .context("failed to decode JSON CustomData")
663 }
664 SerializationEncoding::MsgPack => {
665 let value =
666 codec::deserialize_msgpack_payload::<serde_json::Value>(payload, "CustomData")?;
667 decode_custom_data_value(custom_type_name, &value)
668 .context("failed to decode MsgPack CustomData")
669 }
670 SerializationEncoding::Sbe => {
671 codec::warn_unsupported_inbound(payload_type, encoding);
672 Ok(None)
673 }
674 }
675}
676
677fn decode_custom_data_value(
678 custom_type_name: Ustr,
679 value: &serde_json::Value,
680) -> anyhow::Result<Option<CustomData>> {
681 let Some(data) = deserialize_custom_from_json(custom_type_name.as_str(), value)? else {
682 log::warn!(
683 "External custom payload type '{custom_type_name}' is not registered for inbound republishing"
684 );
685 return Ok(None);
686 };
687
688 let envelope_type_name = value
689 .get("type")
690 .and_then(serde_json::Value::as_str)
691 .context("CustomData JSON missing 'type' field")?;
692 anyhow::ensure!(
693 envelope_type_name == custom_type_name.as_str(),
694 "CustomData envelope type '{envelope_type_name}' does not match message type '{custom_type_name}'"
695 );
696
697 let Data::Custom(custom) = data else {
698 anyhow::bail!("CustomData registry returned non-custom data");
699 };
700
701 Ok(Some(custom))
702}
703
704#[cfg(test)]
705mod tests {
706 use std::{cell::RefCell, rc::Rc};
707
708 use bytes::Bytes;
709 use rstest::rstest;
710 use serde::{Serialize, Serializer};
711
712 use super::*;
713 use crate::msgbus::{
714 MessageBus, TypedHandler, backing::MessageBusExternalEgress, set_message_bus,
715 };
716
717 struct RecordingEgress {
718 messages: Rc<RefCell<Vec<BusMessage>>>,
719 closed: bool,
720 }
721
722 impl MessageBusExternalEgress for RecordingEgress {
723 fn is_closed(&self) -> bool {
724 self.closed
725 }
726
727 fn publish(&self, message: BusMessage) {
728 self.messages.borrow_mut().push(message);
729 }
730
731 fn close(&mut self) {
732 self.closed = true;
733 }
734 }
735
736 struct ReentrantPayload<F>(F);
737
738 impl<F: Fn()> Serialize for ReentrantPayload<F> {
739 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
740 (self.0)();
741 serializer.serialize_u64(37)
742 }
743 }
744
745 #[rstest]
746 fn serialization_can_publish_before_original_egress_delivery() {
747 let messages = install_egress();
748
749 let payload = ReentrantPayload(|| {
750 forward_external_message("nested".into(), BusPayloadType::QuoteTick, &19_u64);
751 });
752
753 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
754
755 let messages = messages.borrow();
756 assert_eq!(messages.len(), 2);
757 assert_message(&messages[0], "nested", b"19");
758 assert_message(&messages[1], "outer", b"37");
759 }
760
761 #[rstest]
762 #[case::close(false)]
763 #[case::dispose(true)]
764 fn serialization_can_close_original_egress(#[case] dispose: bool) {
765 let messages = install_egress();
766
767 let payload = ReentrantPayload(move || {
768 let bus = get_message_bus();
769 let mut bus = bus.borrow_mut();
770 if dispose {
771 bus.dispose();
772 } else {
773 bus.close().unwrap();
774 }
775 });
776
777 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
778
779 assert!(messages.borrow().is_empty());
780 assert!(!get_message_bus().borrow().has_external_egress());
781 }
782
783 #[rstest]
784 fn serialization_keeps_original_destination_when_egress_is_replaced() {
785 let original = install_egress();
786 let replacement = Rc::new(RefCell::new(Vec::new()));
787 let replacement_clone = replacement.clone();
788
789 let payload = ReentrantPayload(move || {
790 get_message_bus().borrow_mut().set_external_egress(
791 Box::new(RecordingEgress {
792 messages: replacement_clone.clone(),
793 closed: false,
794 }),
795 SerializationEncoding::Json,
796 );
797 });
798
799 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
800 forward_external_message("later".into(), BusPayloadType::QuoteTick, &23_u64);
801
802 let original = original.borrow();
803 let replacement = replacement.borrow();
804 assert_eq!(original.len(), 1);
805 assert_message(&original[0], "outer", b"37");
806 assert_eq!(replacement.len(), 1);
807 assert_message(&replacement[0], "later", b"23");
808 }
809
810 #[rstest]
811 #[case(SerializationEncoding::Json)]
812 #[case(SerializationEncoding::MsgPack)]
813 fn test_native_requests_reach_processors_without_stream_registration(
814 #[case] encoding: SerializationEncoding,
815 ) {
816 use nautilus_core::UUID4;
817 use nautilus_model::{data::DataType, identifiers::ClientId};
818
819 use crate::messages::data::RequestCustomData;
820
821 let publications = install_egress();
822 get_message_bus()
823 .borrow_mut()
824 .set_external_egress_config(
825 Box::new(RecordingEgress {
826 messages: publications.clone(),
827 closed: false,
828 }),
829 &crate::msgbus::MessageBusConfig {
830 encoding,
831 external_streams: Some(vec!["external.test".into()]),
832 ..Default::default()
833 },
834 )
835 .unwrap();
836
837 let request = RequestCommand::Data(RequestCustomData::new(
838 ClientId::from("FEED"),
839 DataType::new("DistinctRequest", None, None),
840 None,
841 None,
842 None,
843 UUID4::new(),
844 71.into(),
845 None,
846 ));
847
848 let payload_type = BusPayloadType::RequestCommand;
849 publish_any("commands.data.FEED".into(), &request);
850
851 let message = publications.borrow()[0].clone();
852 let mut received = None;
853 process_external_typed_message(&message, &mut |value, mapping| {
854 let decoded =
855 serde_json::to_value(value.downcast_ref::<RequestCommand>().unwrap()).unwrap();
856
857 received = Some((decoded, mapping.clone()));
858 Ok(())
859 })
860 .unwrap();
861
862 let expected_value = serde_json::to_value(&request).unwrap();
863
864 let mut expected_mapping = expected_value.clone();
865 expected_mapping
866 .as_object_mut()
867 .unwrap()
868 .insert("payload_type".into(), payload_type.as_str().into());
869 assert_eq!(publications.borrow().len(), 1);
870 assert_eq!(message.topic.as_str(), "commands.data.FEED");
871 assert_eq!(message.payload_type, payload_type);
872 assert_eq!(message.encoding, encoding);
873 assert_eq!(received, Some((expected_value, expected_mapping)));
874 }
875
876 #[rstest]
877 #[case(BusPayloadType::RequestCommand, "data request")]
878 #[case(BusPayloadType::DataResponse, "data response")]
879 fn test_native_messages_reject_unsupported_encoding_without_dispatch(
880 #[case] payload_type: BusPayloadType,
881 #[case] name: &str,
882 #[values(SerializationEncoding::Sbe)] encoding: SerializationEncoding,
883 ) {
884 set_message_bus(Rc::new(RefCell::new(MessageBus::default())));
885 get_message_bus()
886 .borrow_mut()
887 .add_streaming_type(payload_type);
888 let message = BusMessage::with_str_topic(
889 "external.test",
890 payload_type,
891 Bytes::from_static(b"unsupported"),
892 encoding,
893 );
894 let expected = format!("{name} encoding is not supported: {encoding}");
895 let mut calls = 0;
896 let error = process_external_typed_message(&message, &mut |_, _| {
897 calls += 1;
898 Ok(())
899 })
900 .unwrap_err();
901
902 assert_eq!(error.to_string(), expected);
903 assert_eq!(
904 republish_external_message(&message)
905 .unwrap_err()
906 .to_string(),
907 expected
908 );
909 assert_eq!(calls, 0);
910 }
911
912 #[rstest]
913 #[case(false)]
914 #[case(true)]
915 fn test_external_source_restored_after_nested_dispatch_and_processor_failure(
916 #[case] panic: bool,
917 ) {
918 use std::panic::{AssertUnwindSafe, catch_unwind};
919
920 use nautilus_core::UUID4;
921 use nautilus_model::{data::DataType, identifiers::ClientId};
922
923 use crate::{
924 messages::data::RequestCustomData,
925 msgbus::{ShareableMessageHandler, subscribe_any},
926 };
927
928 set_message_bus(Rc::new(RefCell::new(MessageBus::default())));
929 get_message_bus()
930 .borrow_mut()
931 .add_streaming_type(BusPayloadType::RequestCommand);
932 let request = RequestCommand::Data(RequestCustomData::new(
933 ClientId::from("FEED"),
934 DataType::new("DistinctRequest", None, None),
935 None,
936 None,
937 None,
938 UUID4::new(),
939 71.into(),
940 None,
941 ));
942 let payload = codec::serialize_payload(
943 SerializationEncoding::Json,
944 BusPayloadType::RequestCommand,
945 &request,
946 )
947 .unwrap();
948 let mut message = BusMessage::with_str_topic(
949 "external.outer",
950 BusPayloadType::RequestCommand,
951 payload,
952 SerializationEncoding::Json,
953 );
954
955 let source = BusMessageSource {
956 publisher_id: "PUBLISHER-017".into(),
957 instance_id: UUID4::new(),
958 stream: "snapshot".into(),
959 sequence: 37,
960 };
961
962 message.source = Some(source);
963 let mut nested = message.clone();
964 nested.topic = "external.nested".into();
965 nested.source = None;
966 let received = Rc::new(RefCell::new(Vec::new()));
967 let shared = received.clone();
968 subscribe_any(
969 nested.topic.as_str().into(),
970 ShareableMessageHandler::from_typed::<RequestCommand, _>(move |_| {
971 shared.borrow_mut().push(current_external_source());
972 }),
973 None,
974 );
975
976 let result = catch_unwind(AssertUnwindSafe(|| {
977 process_external_typed_message(&message, &mut |_, _| {
978 assert_eq!(current_external_source(), Some(source));
979 republish_external_message(&nested)?;
980 assert_eq!(current_external_source(), Some(source));
981 assert_eq!(*received.borrow(), vec![None]);
982
983 assert!(!panic, "processor panicked");
984
985 anyhow::bail!("processor rejected payload")
986 })
987 }));
988
989 if panic {
990 assert_eq!(
991 result.unwrap_err().downcast_ref::<&str>(),
992 Some(&"processor panicked")
993 );
994 } else {
995 assert_eq!(
996 result.unwrap().unwrap_err().to_string(),
997 "processor rejected payload"
998 );
999 }
1000
1001 assert_eq!(current_external_source(), None);
1002 }
1003
1004 #[rstest]
1005 #[case(false, false)]
1006 #[case(true, false)]
1007 #[case(false, true)]
1008 #[case(true, true)]
1009 fn test_native_response_processors_precede_streaming_filters(
1010 #[case] streaming: bool,
1011 #[case] rejected: bool,
1012 ) {
1013 use nautilus_core::UUID4;
1014 use nautilus_model::{data::DataType, identifiers::ClientId};
1015
1016 use crate::{
1017 messages::data::CustomDataResponse,
1018 msgbus::{self, ShareableMessageHandler},
1019 };
1020
1021 set_message_bus(Rc::new(RefCell::new(MessageBus::default())));
1022
1023 if streaming {
1024 get_message_bus()
1025 .borrow_mut()
1026 .add_streaming_type(BusPayloadType::DataResponse);
1027 }
1028
1029 let response = CustomDataResponse::new(
1030 UUID4::new(),
1031 ClientId::from("FEED"),
1032 None,
1033 DataType::new("DistinctReply", None, None),
1034 Bytes::from_static(b"native-bytes"),
1035 Some(67.into()),
1036 Some(71.into()),
1037 73.into(),
1038 None,
1039 );
1040 let publications = Rc::new(RefCell::new(Vec::new()));
1041 get_message_bus()
1042 .borrow_mut()
1043 .set_external_egress_config(
1044 Box::new(RecordingEgress {
1045 messages: publications.clone(),
1046 closed: false,
1047 }),
1048 &crate::msgbus::MessageBusConfig {
1049 encoding: SerializationEncoding::MsgPack,
1050 external_streams: Some(vec!["external.test".into()]),
1051 ..Default::default()
1052 },
1053 )
1054 .unwrap();
1055
1056 publish_any(
1057 "responses.data.FEED".into(),
1058 &DataResponse::Data(response.clone()),
1059 );
1060 let message = publications.borrow()[0].clone();
1061 let published = Rc::new(RefCell::new(0));
1062 let shared = published.clone();
1063 msgbus::subscribe_any(
1064 message.topic.as_str().into(),
1065 ShareableMessageHandler::from_typed::<DataResponse, _>(move |_| {
1066 *shared.borrow_mut() += 1;
1067 }),
1068 None,
1069 );
1070
1071 let mut calls = 0;
1072 let result = process_external_typed_message(&message, &mut |value, mapping| {
1073 calls += 1;
1074
1075 let DataResponse::Data(actual) = value.downcast_ref::<DataResponse>().unwrap() else {
1076 panic!("Expected native custom response");
1077 };
1078
1079 assert_eq!(actual.correlation_id, response.correlation_id);
1080 assert_eq!(
1081 actual.data.downcast_ref::<Bytes>(),
1082 response.data.downcast_ref::<Bytes>()
1083 );
1084 assert_eq!(
1085 mapping,
1086 &serde_json::json!({
1087 "payload_type": "DataResponse",
1088 "Data": {
1089 "correlation_id": response.correlation_id, "client_id": "FEED",
1090 "start": 67, "end": 71, "ts_init": 73, "params": null,
1091 "venue": null, "data_type": response.data_type,
1092 "data": {"Buffer": b"native-bytes".to_vec()},
1093 }
1094 })
1095 );
1096
1097 if rejected {
1098 anyhow::bail!("processor rejected reply");
1099 }
1100
1101 Ok(())
1102 });
1103
1104 assert_eq!(calls, 1);
1105 assert_eq!(publications.borrow().len(), 1);
1106 assert_eq!(message.topic.as_str(), "responses.data.FEED");
1107 assert_eq!(message.payload_type, BusPayloadType::DataResponse);
1108 assert_eq!(message.encoding, SerializationEncoding::MsgPack);
1109 assert_eq!(
1110 result.err().map(|e| e.to_string()),
1111 rejected.then(|| "processor rejected reply".to_string())
1112 );
1113 assert_eq!(*published.borrow(), usize::from(streaming && !rejected));
1114 }
1115
1116 #[rstest]
1117 fn test_startup_dispatches_only_registered_pending_data_responses() {
1118 use nautilus_core::UUID4;
1119 use nautilus_model::{data::DataType, identifiers::ClientId};
1120
1121 use crate::{
1122 messages::data::CustomDataResponse,
1123 msgbus::{self, ShareableMessageHandler},
1124 };
1125
1126 let bus = Rc::new(RefCell::new(MessageBus::default()));
1127 set_message_bus(bus.clone());
1128 bus.borrow_mut()
1129 .add_streaming_type(BusPayloadType::DataResponse);
1130 let id = UUID4::new();
1131 let response = DataResponse::Data(CustomDataResponse::new(
1132 id,
1133 ClientId::from("FEED"),
1134 None,
1135 DataType::new("DistinctReply", None, None),
1136 Bytes::from_static(b"native-bytes"),
1137 None,
1138 None,
1139 73.into(),
1140 None,
1141 ));
1142 let message = BusMessage::with_str_topic(
1143 "responses.data.FEED",
1144 BusPayloadType::DataResponse,
1145 codec::serialize_payload(
1146 SerializationEncoding::MsgPack,
1147 BusPayloadType::DataResponse,
1148 &response,
1149 )
1150 .unwrap(),
1151 SerializationEncoding::MsgPack,
1152 );
1153
1154 let topic_handler = ShareableMessageHandler::from_typed::<DataResponse, _>(|reply| {
1155 msgbus::send_response(reply.correlation_id(), reply);
1156 });
1157
1158 msgbus::subscribe_any(message.topic.as_str().into(), topic_handler, None);
1159 let received = Rc::new(RefCell::new(Vec::new()));
1160 let shared = received.clone();
1161 assert!(!republish_pending_external_response(&message).unwrap());
1162 msgbus::register_response_handler(
1163 &id,
1164 TypedHandler::from(move |envelope: &DataResponseEnvelope| {
1165 let Some(DataResponse::Data(reply)) = envelope.response() else {
1166 panic!("expected custom reply");
1167 };
1168
1169 shared.borrow_mut().push((
1170 reply.correlation_id,
1171 reply.data.downcast_ref::<Bytes>().unwrap().clone(),
1172 reply.ts_init,
1173 ));
1174 }),
1175 );
1176
1177 assert!(republish_pending_external_response(&message).unwrap());
1178 assert!(!republish_pending_external_response(&message).unwrap());
1179 assert_eq!(
1180 *received.borrow(),
1181 vec![(id, Bytes::from_static(b"native-bytes"), 73.into())]
1182 );
1183 }
1184
1185 #[rstest]
1186 #[case(SerializationEncoding::Json)]
1187 #[case(SerializationEncoding::MsgPack)]
1188 fn test_startup_dispatches_payload_free_completion(#[case] encoding: SerializationEncoding) {
1189 use crate::msgbus::{self, ShareableMessageHandler};
1190
1191 let bus = Rc::new(RefCell::new(MessageBus::default()));
1192 set_message_bus(bus.clone());
1193 bus.borrow_mut()
1194 .add_streaming_type(BusPayloadType::DataResponseEnvelope);
1195 let id = nautilus_core::UUID4::new();
1196 let envelope = DataResponseEnvelope::terminal(id, Some("Source unavailable".to_owned()));
1197 let payload =
1198 codec::serialize_payload(encoding, BusPayloadType::DataResponseEnvelope, &envelope)
1199 .unwrap();
1200 let message = BusMessage::with_str_topic(
1201 "responses.data.FEED",
1202 BusPayloadType::DataResponseEnvelope,
1203 payload,
1204 encoding,
1205 );
1206 let received = Rc::new(RefCell::new(Vec::new()));
1207 let shared = received.clone();
1208 msgbus::subscribe_any(
1209 message.topic.as_str().into(),
1210 ShareableMessageHandler::from_typed::<DataResponseEnvelope, _>(
1211 msgbus::send_response_envelope,
1212 ),
1213 None,
1214 );
1215 msgbus::register_response_handler(
1216 &id,
1217 TypedHandler::from(move |response: &DataResponseEnvelope| {
1218 shared.borrow_mut().push(response.clone());
1219 }),
1220 );
1221
1222 assert!(republish_pending_external_response(&message).unwrap());
1223
1224 let received = received.borrow();
1225 assert_eq!(received.len(), 1);
1226 assert_eq!(received[0].correlation_id(), &id);
1227 assert!(received[0].completed());
1228 assert!(received[0].response().is_none());
1229 assert_eq!(received[0].sub_correlation_id(), None);
1230 assert_eq!(received[0].failure_reason(), Some("Source unavailable"));
1231 assert!(bus.borrow().get_response_handler(&id).is_none());
1232 assert!(!republish_pending_external_response(&message).unwrap());
1233 }
1234
1235 fn install_egress() -> Rc<RefCell<Vec<BusMessage>>> {
1236 let messages = Rc::new(RefCell::new(Vec::new()));
1237 let mut bus = MessageBus::default();
1238 bus.set_external_egress(
1239 Box::new(RecordingEgress {
1240 messages: messages.clone(),
1241 closed: false,
1242 }),
1243 SerializationEncoding::Json,
1244 );
1245
1246 set_message_bus(Rc::new(RefCell::new(bus)));
1247 messages
1248 }
1249
1250 fn assert_message(message: &BusMessage, topic: &str, payload: &[u8]) {
1251 assert_eq!(message.topic.as_str(), topic);
1252 assert_eq!(message.payload_type, BusPayloadType::QuoteTick);
1253 assert_eq!(message.encoding, SerializationEncoding::Json);
1254 assert_eq!(message.payload.as_ref(), payload);
1255 }
1256}