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, BusPayloadType, HAS_EXTERNAL_EGRESS, SUPPRESS_EXTERNAL_DEPTH,
33 SuppressExternalGuard,
34 api::{
35 publish_account_state, publish_any, publish_bar, publish_deltas, publish_depth,
36 publish_funding_rate, publish_index_price, publish_instrument, publish_mark_price,
37 publish_option_greeks, publish_order_event, publish_portfolio_snapshot,
38 publish_position_event, publish_quote, publish_trade,
39 },
40 get_message_bus,
41 mstr::{MStr, Topic},
42};
43use crate::{
44 enums::SerializationEncoding,
45 messages::{
46 data::{SubscribeCommand, UnsubscribeCommand},
47 execution::{GenerateExecutionMassStatus, TradingCommand},
48 },
49};
50
51#[inline(always)]
52pub(super) fn forward_to_external_egress<T>(
53 topic: MStr<Topic>,
54 payload_type: BusPayloadType,
55 message: &T,
56) where
57 T: serde::Serialize + Any,
58{
59 if !HAS_EXTERNAL_EGRESS.with(Cell::get) {
60 return;
61 }
62
63 forward_external_message(topic, payload_type, message);
64}
65
66#[inline]
67pub(super) fn forward_any_to_external_egress(topic: MStr<Topic>, message: &dyn Any) {
68 if !HAS_EXTERNAL_EGRESS.with(Cell::get) {
69 return;
70 }
71
72 if let Some(custom) = message.downcast_ref::<CustomData>() {
73 forward_external_message(
74 topic,
75 BusPayloadType::Custom(Ustr::from(custom.data.type_name())),
76 custom,
77 );
78 return;
79 }
80
81 if forward_downcast::<SubscribeCommand>(topic, BusPayloadType::SubscribeCommand, message)
82 || forward_downcast::<UnsubscribeCommand>(
83 topic,
84 BusPayloadType::UnsubscribeCommand,
85 message,
86 )
87 || forward_downcast::<TradingCommand>(topic, BusPayloadType::TradingCommand, message)
88 || forward_downcast::<GenerateExecutionMassStatus>(
89 topic,
90 BusPayloadType::GenerateExecutionMassStatus,
91 message,
92 )
93 || forward_downcast::<OrderStatusReport>(topic, BusPayloadType::OrderStatusReport, message)
94 || forward_downcast::<FillReport>(topic, BusPayloadType::FillReport, message)
95 || forward_downcast::<PositionStatusReport>(
96 topic,
97 BusPayloadType::PositionStatusReport,
98 message,
99 )
100 {
101 return;
102 }
103
104 forward_downcast::<ExecutionMassStatus>(topic, BusPayloadType::ExecutionMassStatus, message);
105}
106
107fn forward_downcast<T>(topic: MStr<Topic>, payload_type: BusPayloadType, message: &dyn Any) -> bool
108where
109 T: serde::Serialize + Any,
110{
111 let Some(message) = message.downcast_ref::<T>() else {
112 return false;
113 };
114
115 forward_external_message(topic, payload_type, message);
116 true
117}
118
119#[cold]
120#[inline(never)]
121fn forward_external_message<T>(topic: MStr<Topic>, payload_type: BusPayloadType, message: &T)
122where
123 T: serde::Serialize + Any,
124{
125 if SUPPRESS_EXTERNAL_DEPTH.with(Cell::get) > 0 {
126 return;
127 }
128
129 let (external_egress, encoding) = {
130 let bus_rc = get_message_bus();
131 let bus = bus_rc.borrow();
132
133 let Some(external_egress) = bus.external_egress() else {
134 return;
135 };
136
137 if payload_type.is_typed_message() && !bus.has_external_streams() {
138 return;
139 }
140
141 if bus.types_filter().contains(&payload_type) {
142 return;
143 }
144
145 (external_egress, bus.encoding_for(payload_type))
146 };
147
148 if external_egress.borrow().is_closed() {
149 return;
150 }
151
152 let payload = match codec::serialize_payload(encoding, payload_type, message) {
153 Ok(payload) => payload,
154 Err(PayloadCodecError::Dropped(e)) => {
155 log::debug!("{e}");
156 return;
157 }
158 Err(PayloadCodecError::Failed(e)) => {
159 log::error!("{e}");
160 return;
161 }
162 };
163
164 let external_egress = external_egress.borrow();
165 if external_egress.is_closed() {
166 return;
167 }
168
169 external_egress.publish(BusMessage::new(*topic, payload_type, payload, encoding));
171}
172
173pub fn republish_external_message(message: &BusMessage) -> anyhow::Result<()> {
186 let topic =
187 MStr::<Topic>::topic_from_ustr(message.topic).context("invalid external message topic")?;
188
189 if !is_registered_streaming_type(message) {
190 return Ok(());
191 }
192
193 let _guard = SuppressExternalGuard::new();
194
195 match message.payload_type {
196 BusPayloadType::Custom(_) => {
197 handle_custom_data(
198 topic,
199 message.payload_type,
200 message.encoding,
201 &message.payload,
202 )?;
203 }
204 BusPayloadType::Instrument => {
205 handle_json_msgpack(
206 topic,
207 message.payload_type,
208 message.encoding,
209 &message.payload,
210 publish_instrument,
211 )?;
212 }
213 BusPayloadType::OrderBookDeltas => handle_market_data(
214 topic,
215 message.encoding,
216 &message.payload,
217 codec::deserialize_order_book_deltas,
218 publish_deltas,
219 )?,
220 BusPayloadType::OrderBookDepth => handle_market_data(
221 topic,
222 message.encoding,
223 &message.payload,
224 codec::deserialize_order_book_depth,
225 publish_depth,
226 )?,
227 BusPayloadType::QuoteTick => handle_market_data(
228 topic,
229 message.encoding,
230 &message.payload,
231 codec::deserialize_quote,
232 publish_quote,
233 )?,
234 BusPayloadType::TradeTick => handle_market_data(
235 topic,
236 message.encoding,
237 &message.payload,
238 codec::deserialize_trade,
239 publish_trade,
240 )?,
241 BusPayloadType::Bar => handle_market_data(
242 topic,
243 message.encoding,
244 &message.payload,
245 codec::deserialize_bar,
246 publish_bar,
247 )?,
248 BusPayloadType::MarkPriceUpdate => handle_market_data(
249 topic,
250 message.encoding,
251 &message.payload,
252 codec::deserialize_mark_price,
253 publish_mark_price,
254 )?,
255 BusPayloadType::IndexPriceUpdate => handle_market_data(
256 topic,
257 message.encoding,
258 &message.payload,
259 codec::deserialize_index_price,
260 publish_index_price,
261 )?,
262 BusPayloadType::FundingRateUpdate => handle_market_data(
263 topic,
264 message.encoding,
265 &message.payload,
266 codec::deserialize_funding_rate,
267 publish_funding_rate,
268 )?,
269 BusPayloadType::OptionGreeks => {
270 handle_market_data(
271 topic,
272 message.encoding,
273 &message.payload,
274 codec::deserialize_option_greeks,
275 publish_option_greeks,
276 )?;
277 }
278 BusPayloadType::AccountState => {
279 handle_json_msgpack(
280 topic,
281 message.payload_type,
282 message.encoding,
283 &message.payload,
284 publish_account_state,
285 )?;
286 }
287 BusPayloadType::OrderEvent => {
288 handle_json_msgpack(
289 topic,
290 message.payload_type,
291 message.encoding,
292 &message.payload,
293 publish_order_event,
294 )?;
295 }
296 BusPayloadType::PositionEvent => {
297 handle_json_msgpack(
298 topic,
299 message.payload_type,
300 message.encoding,
301 &message.payload,
302 publish_position_event,
303 )?;
304 }
305 BusPayloadType::PortfolioSnapshot => {
306 handle_json_msgpack(
307 topic,
308 message.payload_type,
309 message.encoding,
310 &message.payload,
311 publish_portfolio_snapshot,
312 )?;
313 }
314 BusPayloadType::SubscribeCommand => {
315 handle_json_msgpack_any::<SubscribeCommand>(topic, message)?;
316 }
317 BusPayloadType::UnsubscribeCommand => {
318 handle_json_msgpack_any::<UnsubscribeCommand>(topic, message)?;
319 }
320 BusPayloadType::TradingCommand => {
321 handle_json_msgpack_any::<TradingCommand>(topic, message)?;
322 }
323 BusPayloadType::GenerateExecutionMassStatus => {
324 handle_json_msgpack_any::<GenerateExecutionMassStatus>(topic, message)?;
325 }
326 BusPayloadType::OrderStatusReport => {
327 handle_json_msgpack_any::<OrderStatusReport>(topic, message)?;
328 }
329 BusPayloadType::FillReport => {
330 handle_json_msgpack_any::<FillReport>(topic, message)?;
331 }
332 BusPayloadType::PositionStatusReport => {
333 handle_json_msgpack_any::<PositionStatusReport>(topic, message)?;
334 }
335 BusPayloadType::ExecutionMassStatus => {
336 handle_json_msgpack_any::<ExecutionMassStatus>(topic, message)?;
337 }
338 #[cfg(feature = "defi")]
339 BusPayloadType::Block
340 | BusPayloadType::Pool
341 | BusPayloadType::PoolLiquidityUpdate
342 | BusPayloadType::PoolFeeCollect
343 | BusPayloadType::PoolFlash => {
344 crate::defi::msgbus::republish_external_message(
345 topic,
346 message.payload_type,
347 message.encoding,
348 &message.payload,
349 )?;
350 }
351 }
352
353 Ok(())
354}
355
356pub fn process_external_typed_message(
376 message: &BusMessage,
377 processor: &mut dyn FnMut(&dyn Any, &serde_json::Value) -> anyhow::Result<()>,
378) -> anyhow::Result<()> {
379 let topic =
380 MStr::<Topic>::topic_from_ustr(message.topic).context("invalid external message topic")?;
381 let _guard = SuppressExternalGuard::new();
382
383 #[rustfmt::skip]
384 let result = match message.payload_type {
385 BusPayloadType::SubscribeCommand => process_typed_payload::<SubscribeCommand>(topic, message, processor),
386 BusPayloadType::UnsubscribeCommand => process_typed_payload::<UnsubscribeCommand>(topic, message, processor),
387 BusPayloadType::TradingCommand => process_typed_payload::<TradingCommand>(topic, message, processor),
388 BusPayloadType::GenerateExecutionMassStatus => process_typed_payload::<GenerateExecutionMassStatus>(topic, message, processor),
389 BusPayloadType::OrderStatusReport => process_typed_payload::<OrderStatusReport>(topic, message, processor),
390 BusPayloadType::FillReport => process_typed_payload::<FillReport>(topic, message, processor),
391 BusPayloadType::PositionStatusReport => process_typed_payload::<PositionStatusReport>(topic, message, processor),
392 BusPayloadType::ExecutionMassStatus => process_typed_payload::<ExecutionMassStatus>(topic, message, processor),
393 _ => republish_external_message(message),
394 };
395 result
396}
397
398fn handle_json_msgpack_any<T>(topic: MStr<Topic>, message: &BusMessage) -> anyhow::Result<()>
399where
400 T: DeserializeOwned + Any,
401{
402 handle_json_msgpack(
403 topic,
404 message.payload_type,
405 message.encoding,
406 &message.payload,
407 |topic, value: &T| publish_any(topic, value),
408 )
409}
410
411fn process_typed_payload<T>(
412 topic: MStr<Topic>,
413 message: &BusMessage,
414 processor: &mut dyn FnMut(&dyn Any, &serde_json::Value) -> anyhow::Result<()>,
415) -> anyhow::Result<()>
416where
417 T: DeserializeOwned + serde::Serialize + Any,
418{
419 let Some(value) = codec::deserialize_json_msgpack_payload::<T>(
420 message.payload_type,
421 message.encoding,
422 &message.payload,
423 )?
424 else {
425 return Ok(());
426 };
427 let mut mapping = serde_json::to_value(&value).with_context(|| {
428 format!(
429 "failed to map decoded {} stream payload",
430 message.payload_type
431 )
432 })?;
433 let mapping_object = mapping.as_object_mut().with_context(|| {
434 format!(
435 "decoded {} stream payload did not map to an object",
436 message.payload_type
437 )
438 })?;
439 anyhow::ensure!(
440 !mapping_object.contains_key("payload_type"),
441 "decoded {} stream payload contains reserved payload_type field",
442 message.payload_type
443 );
444 mapping_object.insert(
445 "payload_type".to_string(),
446 serde_json::Value::String(message.payload_type.as_str().to_string()),
447 );
448
449 processor(&value, &mapping)?;
450 if is_registered_streaming_type(message) {
451 publish_any(topic, &value);
452 }
453 Ok(())
454}
455
456fn is_registered_streaming_type(message: &BusMessage) -> bool {
457 if get_message_bus()
458 .borrow()
459 .is_streaming_type(message.payload_type)
460 {
461 return true;
462 }
463
464 let type_name = message.payload_type.as_str();
465 if type_name.is_empty() {
466 log::debug!(
467 "Skipping external message on topic '{}' with no payload type for inbound republishing",
468 message.topic
469 );
470 } else {
471 log::debug!(
472 "Skipping external {type_name} message on topic '{}' because the type is not registered for streaming",
473 message.topic
474 );
475 }
476
477 false
478}
479
480pub(crate) fn handle_json_msgpack<T>(
481 topic: MStr<Topic>,
482 payload_type: BusPayloadType,
483 encoding: SerializationEncoding,
484 payload: &[u8],
485 publish: impl FnOnce(MStr<Topic>, &T),
486) -> anyhow::Result<()>
487where
488 T: DeserializeOwned,
489{
490 let Some(value) = codec::deserialize_json_msgpack_payload(payload_type, encoding, payload)?
491 else {
492 return Ok(());
493 };
494
495 publish(topic, &value);
496 Ok(())
497}
498
499fn handle_market_data<T>(
500 topic: MStr<Topic>,
501 encoding: SerializationEncoding,
502 payload: &[u8],
503 deserialize: fn(SerializationEncoding, &[u8]) -> anyhow::Result<Option<T>>,
504 publish: impl FnOnce(MStr<Topic>, &T),
505) -> anyhow::Result<()> {
506 let Some(value) = deserialize(encoding, payload)? else {
507 return Ok(());
508 };
509
510 publish(topic, &value);
511 Ok(())
512}
513
514fn handle_custom_data(
515 topic: MStr<Topic>,
516 payload_type: BusPayloadType,
517 encoding: SerializationEncoding,
518 payload: &[u8],
519) -> anyhow::Result<()> {
520 let Some(custom) = decode_custom_data_payload(payload_type, encoding, payload)? else {
521 return Ok(());
522 };
523
524 publish_any(topic, &custom);
525 Ok(())
526}
527
528fn decode_custom_data_payload(
529 payload_type: BusPayloadType,
530 encoding: SerializationEncoding,
531 payload: &[u8],
532) -> anyhow::Result<Option<CustomData>> {
533 let BusPayloadType::Custom(custom_type_name) = payload_type else {
534 unreachable!("custom data payload decoding requires a custom payload type");
535 };
536
537 if custom_type_name.is_empty() {
538 log::warn!("External payload has no type for inbound republishing");
539 return Ok(None);
540 } else if !payload_type.supports(encoding) {
541 codec::warn_unsupported_inbound(payload_type, encoding);
542 return Ok(None);
543 }
544
545 match encoding {
546 SerializationEncoding::Json => {
547 let value =
548 codec::deserialize_json_payload::<serde_json::Value>(payload, "CustomData")?;
549 decode_custom_data_value(custom_type_name, &value)
550 .context("failed to decode JSON CustomData")
551 }
552 SerializationEncoding::MsgPack => {
553 let value =
554 codec::deserialize_msgpack_payload::<serde_json::Value>(payload, "CustomData")?;
555 decode_custom_data_value(custom_type_name, &value)
556 .context("failed to decode MsgPack CustomData")
557 }
558 SerializationEncoding::Sbe | SerializationEncoding::Capnp => {
559 codec::warn_unsupported_inbound(payload_type, encoding);
560 Ok(None)
561 }
562 }
563}
564
565fn decode_custom_data_value(
566 custom_type_name: Ustr,
567 value: &serde_json::Value,
568) -> anyhow::Result<Option<CustomData>> {
569 let Some(data) = deserialize_custom_from_json(custom_type_name.as_str(), value)? else {
570 log::warn!(
571 "External custom payload type '{custom_type_name}' is not registered for inbound republishing"
572 );
573 return Ok(None);
574 };
575
576 let envelope_type_name = value
577 .get("type")
578 .and_then(serde_json::Value::as_str)
579 .context("CustomData JSON missing 'type' field")?;
580 anyhow::ensure!(
581 envelope_type_name == custom_type_name.as_str(),
582 "CustomData envelope type '{envelope_type_name}' does not match message type '{custom_type_name}'"
583 );
584
585 let Data::Custom(custom) = data else {
586 anyhow::bail!("CustomData registry returned non-custom data");
587 };
588
589 Ok(Some(custom))
590}
591
592#[cfg(test)]
593mod tests {
594 use std::{cell::RefCell, rc::Rc};
595
596 use rstest::rstest;
597 use serde::{Serialize, Serializer};
598
599 use super::*;
600 use crate::msgbus::{MessageBus, backing::MessageBusExternalEgress, set_message_bus};
601
602 struct RecordingEgress {
603 messages: Rc<RefCell<Vec<BusMessage>>>,
604 closed: bool,
605 }
606
607 impl MessageBusExternalEgress for RecordingEgress {
608 fn is_closed(&self) -> bool {
609 self.closed
610 }
611
612 fn publish(&self, message: BusMessage) {
613 self.messages.borrow_mut().push(message);
614 }
615
616 fn close(&mut self) {
617 self.closed = true;
618 }
619 }
620
621 struct ReentrantPayload<F>(F);
622
623 impl<F: Fn()> Serialize for ReentrantPayload<F> {
624 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
625 (self.0)();
626 serializer.serialize_u64(37)
627 }
628 }
629
630 #[rstest]
631 fn serialization_can_publish_before_original_egress_delivery() {
632 let messages = install_egress();
633
634 let payload = ReentrantPayload(|| {
635 forward_external_message("nested".into(), BusPayloadType::QuoteTick, &19_u64);
636 });
637
638 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
639
640 let messages = messages.borrow();
641 assert_eq!(messages.len(), 2);
642 assert_message(&messages[0], "nested", b"19");
643 assert_message(&messages[1], "outer", b"37");
644 }
645
646 #[rstest]
647 #[case::close(false)]
648 #[case::dispose(true)]
649 fn serialization_can_close_original_egress(#[case] dispose: bool) {
650 let messages = install_egress();
651
652 let payload = ReentrantPayload(move || {
653 let bus = get_message_bus();
654 let mut bus = bus.borrow_mut();
655
656 if dispose {
657 bus.dispose();
658 } else {
659 bus.close().unwrap();
660 }
661 });
662
663 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
664
665 assert!(messages.borrow().is_empty());
666 assert!(!get_message_bus().borrow().has_external_egress());
667 }
668
669 #[rstest]
670 fn serialization_keeps_original_destination_when_egress_is_replaced() {
671 let original = install_egress();
672 let replacement = Rc::new(RefCell::new(Vec::new()));
673 let replacement_clone = replacement.clone();
674
675 let payload = ReentrantPayload(move || {
676 get_message_bus().borrow_mut().set_external_egress(
677 Box::new(RecordingEgress {
678 messages: replacement_clone.clone(),
679 closed: false,
680 }),
681 SerializationEncoding::Json,
682 );
683 });
684
685 forward_external_message("outer".into(), BusPayloadType::QuoteTick, &payload);
686 forward_external_message("later".into(), BusPayloadType::QuoteTick, &23_u64);
687
688 let original = original.borrow();
689 let replacement = replacement.borrow();
690 assert_eq!(original.len(), 1);
691 assert_message(&original[0], "outer", b"37");
692 assert_eq!(replacement.len(), 1);
693 assert_message(&replacement[0], "later", b"23");
694 }
695
696 fn install_egress() -> Rc<RefCell<Vec<BusMessage>>> {
697 let messages = Rc::new(RefCell::new(Vec::new()));
698 let mut bus = MessageBus::default();
699 bus.set_external_egress(
700 Box::new(RecordingEgress {
701 messages: messages.clone(),
702 closed: false,
703 }),
704 SerializationEncoding::Json,
705 );
706
707 set_message_bus(Rc::new(RefCell::new(bus)));
708 messages
709 }
710
711 fn assert_message(message: &BusMessage, topic: &str, payload: &[u8]) {
712 assert_eq!(message.topic.as_str(), topic);
713 assert_eq!(message.payload_type, BusPayloadType::QuoteTick);
714 assert_eq!(message.encoding, SerializationEncoding::Json);
715 assert_eq!(message.payload.as_ref(), payload);
716 }
717}