1use std::fmt::Display;
17
18use bytes::Bytes;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use super::switchboard::CLOSE_TOPIC;
23use crate::enums::SerializationEncoding;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub enum BusPayloadCategory {
28 MarketData,
29 BuiltIn,
30 Other,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum BusPayloadType {
40 Custom(Ustr),
42 Instrument,
43 OrderBookDeltas,
44 OrderBookDepth10,
45 QuoteTick,
46 TradeTick,
47 Bar,
48 MarkPriceUpdate,
49 IndexPriceUpdate,
50 FundingRateUpdate,
51 OptionGreeks,
52 AccountState,
53 OrderEvent,
54 PositionEvent,
55 PortfolioSnapshot,
56 #[cfg(feature = "defi")]
57 Block,
58 #[cfg(feature = "defi")]
59 Pool,
60 #[cfg(feature = "defi")]
61 PoolLiquidityUpdate,
62 #[cfg(feature = "defi")]
63 PoolFeeCollect,
64 #[cfg(feature = "defi")]
65 PoolFlash,
66}
67
68impl BusPayloadType {
69 pub(crate) const PUBLISHED_TYPES: &'static [Self] = &[
70 Self::Instrument,
71 Self::OrderBookDeltas,
72 Self::OrderBookDepth10,
73 Self::QuoteTick,
74 Self::TradeTick,
75 Self::Bar,
76 Self::MarkPriceUpdate,
77 Self::IndexPriceUpdate,
78 Self::FundingRateUpdate,
79 Self::OptionGreeks,
80 Self::AccountState,
81 Self::OrderEvent,
82 Self::PositionEvent,
83 Self::PortfolioSnapshot,
84 #[cfg(feature = "defi")]
85 Self::Block,
86 #[cfg(feature = "defi")]
87 Self::Pool,
88 #[cfg(feature = "defi")]
89 Self::PoolLiquidityUpdate,
90 #[cfg(feature = "defi")]
91 Self::PoolFeeCollect,
92 #[cfg(feature = "defi")]
93 Self::PoolFlash,
94 ];
95
96 #[must_use]
98 pub fn as_str(&self) -> &str {
99 match self {
100 Self::Custom(type_name) => type_name.as_str(),
101 Self::Instrument => "InstrumentAny",
102 Self::OrderBookDeltas => "OrderBookDeltas",
103 Self::OrderBookDepth10 => "OrderBookDepth10",
104 Self::QuoteTick => "QuoteTick",
105 Self::TradeTick => "TradeTick",
106 Self::Bar => "Bar",
107 Self::MarkPriceUpdate => "MarkPriceUpdate",
108 Self::IndexPriceUpdate => "IndexPriceUpdate",
109 Self::FundingRateUpdate => "FundingRateUpdate",
110 Self::OptionGreeks => "OptionGreeks",
111 Self::AccountState => "AccountState",
112 Self::OrderEvent => "OrderEventAny",
113 Self::PositionEvent => "PositionEvent",
114 Self::PortfolioSnapshot => "PortfolioSnapshot",
115 #[cfg(feature = "defi")]
116 Self::Block => "Block",
117 #[cfg(feature = "defi")]
118 Self::Pool => "Pool",
119 #[cfg(feature = "defi")]
120 Self::PoolLiquidityUpdate => "PoolLiquidityUpdate",
121 #[cfg(feature = "defi")]
122 Self::PoolFeeCollect => "PoolFeeCollect",
123 #[cfg(feature = "defi")]
124 Self::PoolFlash => "PoolFlash",
125 }
126 }
127
128 #[must_use]
132 pub fn from_name(name: &str) -> Self {
133 match name {
134 "InstrumentAny" => Self::Instrument,
135 "OrderBookDeltas" => Self::OrderBookDeltas,
136 "OrderBookDepth10" => Self::OrderBookDepth10,
137 "QuoteTick" => Self::QuoteTick,
138 "TradeTick" => Self::TradeTick,
139 "Bar" => Self::Bar,
140 "MarkPriceUpdate" => Self::MarkPriceUpdate,
141 "IndexPriceUpdate" => Self::IndexPriceUpdate,
142 "FundingRateUpdate" => Self::FundingRateUpdate,
143 "OptionGreeks" => Self::OptionGreeks,
144 "AccountState" => Self::AccountState,
145 "OrderEventAny" => Self::OrderEvent,
146 "PositionEvent" => Self::PositionEvent,
147 "PortfolioSnapshot" => Self::PortfolioSnapshot,
148 #[cfg(feature = "defi")]
149 "Block" => Self::Block,
150 #[cfg(feature = "defi")]
151 "Pool" => Self::Pool,
152 #[cfg(feature = "defi")]
153 "PoolLiquidityUpdate" => Self::PoolLiquidityUpdate,
154 #[cfg(feature = "defi")]
155 "PoolFeeCollect" => Self::PoolFeeCollect,
156 #[cfg(feature = "defi")]
157 "PoolFlash" => Self::PoolFlash,
158 other => Self::Custom(Ustr::from(other)),
159 }
160 }
161
162 #[must_use]
164 pub fn category(&self) -> BusPayloadCategory {
165 if self.has_bus_binary_schema() {
166 BusPayloadCategory::MarketData
167 } else {
168 match self {
169 Self::AccountState
170 | Self::OrderEvent
171 | Self::PositionEvent
172 | Self::PortfolioSnapshot => BusPayloadCategory::BuiltIn,
173 _ => BusPayloadCategory::Other,
174 }
175 }
176 }
177
178 #[must_use]
180 pub fn supports(&self, encoding: SerializationEncoding) -> bool {
181 match encoding {
182 SerializationEncoding::Json | SerializationEncoding::MsgPack => true,
183 SerializationEncoding::Sbe => cfg!(feature = "sbe") && self.has_bus_binary_schema(),
184 SerializationEncoding::Capnp => cfg!(feature = "capnp") && self.has_bus_binary_schema(),
185 }
186 }
187
188 fn has_bus_binary_schema(&self) -> bool {
189 matches!(
190 self,
191 Self::OrderBookDeltas
192 | Self::OrderBookDepth10
193 | Self::QuoteTick
194 | Self::TradeTick
195 | Self::Bar
196 | Self::MarkPriceUpdate
197 | Self::IndexPriceUpdate
198 | Self::FundingRateUpdate
199 | Self::OptionGreeks
200 )
201 }
202}
203
204impl Display for BusPayloadType {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 f.write_str(self.as_str())
207 }
208}
209
210impl Serialize for BusPayloadType {
211 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
212 serializer.serialize_str(self.as_str())
213 }
214}
215
216impl<'de> Deserialize<'de> for BusPayloadType {
217 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
218 let name = String::deserialize(deserializer)?;
219 Ok(Self::from_name(&name))
220 }
221}
222
223#[derive(Clone, Debug, Serialize, Deserialize)]
228#[cfg_attr(
229 feature = "python",
230 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.common", from_py_object)
231)]
232#[cfg_attr(
233 feature = "python",
234 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
235)]
236pub struct BusMessage {
237 pub topic: Ustr,
239 pub payload_type: BusPayloadType,
242 pub payload: Bytes,
244 pub encoding: SerializationEncoding,
247}
248
249impl BusMessage {
250 pub fn new(
252 topic: Ustr,
253 payload_type: BusPayloadType,
254 payload: Bytes,
255 encoding: SerializationEncoding,
256 ) -> Self {
257 debug_assert!(!topic.is_empty());
258 Self {
259 topic,
260 payload_type,
261 payload,
262 encoding,
263 }
264 }
265
266 pub fn with_str_topic<T: AsRef<str>>(
271 topic: T,
272 payload_type: BusPayloadType,
273 payload: Bytes,
274 encoding: SerializationEncoding,
275 ) -> Self {
276 Self::new(Ustr::from(topic.as_ref()), payload_type, payload, encoding)
277 }
278
279 pub fn new_close() -> Self {
281 Self::with_str_topic(
282 CLOSE_TOPIC,
283 BusPayloadType::Custom(Ustr::default()),
284 Bytes::new(),
285 SerializationEncoding::default(),
286 )
287 }
288}
289
290impl Display for BusMessage {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 write!(
293 f,
294 "[{}] {} {} {}",
295 self.topic,
296 self.payload_type.as_str(),
297 String::from_utf8_lossy(&self.payload),
298 self.encoding
299 )
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use bytes::Bytes;
306 use rstest::rstest;
307
308 use super::*;
309
310 #[rstest]
311 #[case("test/topic", "payload data")]
312 #[case("events/trading", "Another payload")]
313 fn test_with_str_topic_str(#[case] topic: &str, #[case] payload_str: &str) {
314 let payload = Bytes::from(payload_str.to_string());
315
316 let message = BusMessage::with_str_topic(
317 topic,
318 BusPayloadType::QuoteTick,
319 payload.clone(),
320 SerializationEncoding::Json,
321 );
322
323 assert_eq!(message.topic.as_str(), topic);
324 assert_eq!(message.payload_type, BusPayloadType::QuoteTick);
325 assert_eq!(message.encoding, SerializationEncoding::Json);
326 assert_eq!(message.payload, payload);
327 }
328
329 #[rstest]
330 fn test_with_str_topic_string() {
331 let topic_string = String::from("orders/new");
332 let payload = Bytes::from("order payload data");
333
334 let message = BusMessage::with_str_topic(
335 topic_string.clone(),
336 BusPayloadType::OrderEvent,
337 payload.clone(),
338 SerializationEncoding::MsgPack,
339 );
340
341 assert_eq!(message.topic.as_str(), topic_string);
342 assert_eq!(message.payload_type, BusPayloadType::OrderEvent);
343 assert_eq!(message.encoding, SerializationEncoding::MsgPack);
344 assert_eq!(message.payload, payload);
345 }
346
347 #[rstest]
348 fn test_new_close() {
349 let message = BusMessage::new_close();
350
351 assert_eq!(message.topic.as_str(), "CLOSE");
352 assert!(message.payload.is_empty());
353 }
354
355 #[rstest]
356 #[case(BusPayloadType::QuoteTick, BusPayloadCategory::MarketData)]
357 #[case(BusPayloadType::OrderBookDeltas, BusPayloadCategory::MarketData)]
358 #[case(BusPayloadType::AccountState, BusPayloadCategory::BuiltIn)]
359 #[case(BusPayloadType::OrderEvent, BusPayloadCategory::BuiltIn)]
360 #[case(BusPayloadType::Instrument, BusPayloadCategory::Other)]
361 #[case(BusPayloadType::OptionGreeks, BusPayloadCategory::MarketData)]
362 #[case(
363 BusPayloadType::Custom(Ustr::from("CustomPayload")),
364 BusPayloadCategory::Other
365 )]
366 fn bus_payload_type_category(
367 #[case] payload_type: BusPayloadType,
368 #[case] expected: BusPayloadCategory,
369 ) {
370 assert_eq!(payload_type.category(), expected);
371 }
372
373 #[rstest]
374 #[case(BusPayloadType::QuoteTick, SerializationEncoding::Json, true)]
375 #[case(BusPayloadType::QuoteTick, SerializationEncoding::MsgPack, true)]
376 #[cfg_attr(
377 feature = "sbe",
378 case(BusPayloadType::QuoteTick, SerializationEncoding::Sbe, true)
379 )]
380 #[cfg_attr(
381 not(feature = "sbe"),
382 case(BusPayloadType::QuoteTick, SerializationEncoding::Sbe, false)
383 )]
384 #[cfg_attr(
385 feature = "capnp",
386 case(BusPayloadType::QuoteTick, SerializationEncoding::Capnp, true)
387 )]
388 #[cfg_attr(
389 not(feature = "capnp"),
390 case(BusPayloadType::QuoteTick, SerializationEncoding::Capnp, false)
391 )]
392 #[case(BusPayloadType::AccountState, SerializationEncoding::Json, true)]
393 #[case(BusPayloadType::AccountState, SerializationEncoding::Capnp, false)]
394 #[case(BusPayloadType::Instrument, SerializationEncoding::Sbe, false)]
395 #[cfg_attr(
396 feature = "sbe",
397 case(BusPayloadType::OptionGreeks, SerializationEncoding::Sbe, true)
398 )]
399 #[cfg_attr(
400 not(feature = "sbe"),
401 case(BusPayloadType::OptionGreeks, SerializationEncoding::Sbe, false)
402 )]
403 #[cfg_attr(
404 feature = "capnp",
405 case(BusPayloadType::OptionGreeks, SerializationEncoding::Capnp, true)
406 )]
407 #[cfg_attr(
408 not(feature = "capnp"),
409 case(BusPayloadType::OptionGreeks, SerializationEncoding::Capnp, false)
410 )]
411 #[case(
412 BusPayloadType::Custom(Ustr::from("CustomPayload")),
413 SerializationEncoding::MsgPack,
414 true
415 )]
416 #[case(
417 BusPayloadType::Custom(Ustr::from("CustomPayload")),
418 SerializationEncoding::Sbe,
419 false
420 )]
421 fn bus_payload_type_supports_encoding(
422 #[case] payload_type: BusPayloadType,
423 #[case] encoding: SerializationEncoding,
424 #[case] expected: bool,
425 ) {
426 assert_eq!(payload_type.supports(encoding), expected);
427 }
428}