1pub mod bar;
19pub mod bet;
20pub mod black_scholes;
21pub mod close;
22pub mod custom;
23pub mod delta;
24pub mod deltas;
25pub mod depth;
26pub mod forward;
27pub mod funding;
28pub mod greeks;
29pub mod option_chain;
30pub mod order;
31pub mod prices;
32pub mod quote;
33pub mod registry;
34pub mod status;
35pub mod trade;
36
37#[cfg(any(test, feature = "stubs"))]
38pub mod stubs;
39
40use std::{
41 fmt::{Debug, Display},
42 hash::{Hash, Hasher},
43 str::FromStr,
44};
45
46use nautilus_core::{Params, UnixNanos};
47use serde::{
48 Deserialize, Serialize,
49 de::{self, IgnoredAny, MapAccess, SeqAccess, Visitor},
50};
51use serde_json::Value as JsonValue;
52
53#[cfg(feature = "defi")]
54use crate::defi::DefiData;
55#[rustfmt::skip] pub use bar::{Bar, BarSpecification, BarType};
58pub use black_scholes::Greeks;
59pub use close::InstrumentClose;
60#[cfg(feature = "python")]
61pub use custom::PythonCustomDataWrapper;
62pub use custom::{
63 CustomData, CustomDataTrait, ensure_custom_data_json_registered, register_custom_data_json,
64};
65#[cfg(feature = "python")]
66pub use custom::{
67 get_python_data_class, reconstruct_python_custom_data, register_python_data_class,
68};
69pub use delta::OrderBookDelta;
70pub use deltas::OrderBookDeltas;
71pub use depth::{DEPTH10_LEN, OrderBookDepth10};
72pub use forward::ForwardPrice;
73pub use funding::FundingRateUpdate;
74pub use greeks::{
75 BlackScholesGreeksResult, GreeksData, HasGreeks, OptionGreekValues, PortfolioGreeks,
76 YieldCurveData, black_scholes_greeks, imply_vol_and_greeks, refine_vol_and_greeks,
77};
78pub use option_chain::{OptionChainSlice, OptionGreeks, OptionStrikeData, StrikeRange};
79pub use order::{BookOrder, NULL_ORDER};
80pub use prices::{IndexPriceUpdate, MarkPriceUpdate};
81pub use quote::QuoteTick;
82#[cfg(feature = "arrow")]
83pub use registry::{
84 ArrowDecoder, ArrowEncoder, decode_custom_from_arrow, encode_custom_to_arrow,
85 ensure_arrow_registered, get_arrow_schema, register_arrow,
86};
87#[cfg(feature = "python")]
88pub use registry::{
89 PyExtractor, ensure_py_extractor_registered, ensure_rust_extractor_factory_registered,
90 ensure_rust_extractor_registered, get_rust_extractor, register_py_extractor,
91 register_rust_extractor, register_rust_extractor_factory, try_extract_from_py,
92};
93pub use registry::{
94 deserialize_custom_from_json, ensure_json_deserializer_registered, register_json_deserializer,
95};
96pub use status::InstrumentStatus;
97pub use trade::TradeTick;
98
99use crate::identifiers::{InstrumentId, Venue};
100#[derive(Debug)]
105pub enum Data {
106 Delta(OrderBookDelta),
107 Deltas(Box<OrderBookDeltas>),
108 Depth10(Box<OrderBookDepth10>), Quote(QuoteTick),
110 Trade(TradeTick),
111 Bar(Bar),
112 MarkPrice(MarkPriceUpdate),
113 IndexPrice(IndexPriceUpdate),
114 FundingRate(FundingRateUpdate),
115 OptionGreeks(OptionGreeks),
116 InstrumentStatus(InstrumentStatus),
117 InstrumentClose(InstrumentClose),
118 Custom(CustomData),
119 #[cfg(feature = "defi")]
120 Defi(Box<DefiData>), }
122
123impl<'de> Deserialize<'de> for Data {
124 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125 where
126 D: serde::Deserializer<'de>,
127 {
128 use serde::de::Error;
129 let value = serde_json::Value::deserialize(deserializer)?;
130 let type_name = value
131 .get("type")
132 .and_then(|v| v.as_str())
133 .ok_or_else(|| D::Error::custom("Missing 'type' field in Data"))?
134 .to_string();
135
136 match type_name.as_str() {
137 "OrderBookDelta" => Ok(Self::Delta(
138 serde_json::from_value(value).map_err(D::Error::custom)?,
139 )),
140 "OrderBookDeltas" => Ok(Self::Deltas(
141 serde_json::from_value(value).map_err(D::Error::custom)?,
142 )),
143 "OrderBookDepth10" => Ok(Self::Depth10(
144 serde_json::from_value(value).map_err(D::Error::custom)?,
145 )),
146 "QuoteTick" => Ok(Self::Quote(
147 serde_json::from_value(value).map_err(D::Error::custom)?,
148 )),
149 "TradeTick" => Ok(Self::Trade(
150 serde_json::from_value(value).map_err(D::Error::custom)?,
151 )),
152 "Bar" => Ok(Self::Bar(
153 serde_json::from_value(value).map_err(D::Error::custom)?,
154 )),
155 "MarkPriceUpdate" => Ok(Self::MarkPrice(
156 serde_json::from_value(value).map_err(D::Error::custom)?,
157 )),
158 "IndexPriceUpdate" => Ok(Self::IndexPrice(
159 serde_json::from_value(value).map_err(D::Error::custom)?,
160 )),
161 "FundingRateUpdate" => Ok(Self::FundingRate(
162 serde_json::from_value(value).map_err(D::Error::custom)?,
163 )),
164 "OptionGreeks" => Ok(Self::OptionGreeks(
165 serde_json::from_value(value).map_err(D::Error::custom)?,
166 )),
167 "InstrumentStatus" => Ok(Self::InstrumentStatus(
168 serde_json::from_value(value).map_err(D::Error::custom)?,
169 )),
170 "InstrumentClose" => Ok(Self::InstrumentClose(
171 serde_json::from_value(value).map_err(D::Error::custom)?,
172 )),
173 _ => {
174 if let Some(data) =
175 deserialize_custom_from_json(&type_name, &value).map_err(D::Error::custom)?
176 {
177 Ok(data)
178 } else {
179 Err(D::Error::custom(format!("Unknown Data type: {type_name}")))
180 }
181 }
182 }
183 }
184}
185
186impl Clone for Data {
187 fn clone(&self) -> Self {
188 match self {
189 Self::Delta(x) => Self::Delta(*x),
190 Self::Deltas(x) => Self::Deltas(x.clone()),
191 Self::Depth10(x) => Self::Depth10(x.clone()),
192 Self::Quote(x) => Self::Quote(*x),
193 Self::Trade(x) => Self::Trade(*x),
194 Self::Bar(x) => Self::Bar(*x),
195 Self::MarkPrice(x) => Self::MarkPrice(*x),
196 Self::IndexPrice(x) => Self::IndexPrice(*x),
197 Self::FundingRate(x) => Self::FundingRate(*x),
198 Self::OptionGreeks(x) => Self::OptionGreeks(*x),
199 Self::InstrumentStatus(x) => Self::InstrumentStatus(*x),
200 Self::InstrumentClose(x) => Self::InstrumentClose(*x),
201 Self::Custom(x) => Self::Custom(x.clone()),
202 #[cfg(feature = "defi")]
203 Self::Defi(x) => Self::Defi(x.clone()),
204 }
205 }
206}
207
208impl PartialEq for Data {
209 fn eq(&self, other: &Self) -> bool {
210 match (self, other) {
211 (Self::Delta(a), Self::Delta(b)) => a == b,
212 (Self::Deltas(a), Self::Deltas(b)) => a == b,
213 (Self::Depth10(a), Self::Depth10(b)) => a == b,
214 (Self::Quote(a), Self::Quote(b)) => a == b,
215 (Self::Trade(a), Self::Trade(b)) => a == b,
216 (Self::Bar(a), Self::Bar(b)) => a == b,
217 (Self::MarkPrice(a), Self::MarkPrice(b)) => a == b,
218 (Self::IndexPrice(a), Self::IndexPrice(b)) => a == b,
219 (Self::FundingRate(a), Self::FundingRate(b)) => a == b,
220 (Self::OptionGreeks(a), Self::OptionGreeks(b)) => a == b,
221 (Self::InstrumentStatus(a), Self::InstrumentStatus(b)) => a == b,
222 (Self::InstrumentClose(a), Self::InstrumentClose(b)) => a == b,
223 (Self::Custom(a), Self::Custom(b)) => a == b,
224 #[cfg(feature = "defi")]
225 (Self::Defi(a), Self::Defi(b)) => a == b,
226 _ => false,
227 }
228 }
229}
230
231impl Serialize for Data {
232 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233 where
234 S: serde::Serializer,
235 {
236 match self {
237 Self::Delta(x) => x.serialize(serializer),
238 Self::Deltas(x) => x.serialize(serializer),
239 Self::Depth10(x) => x.serialize(serializer),
240 Self::Quote(x) => x.serialize(serializer),
241 Self::Trade(x) => x.serialize(serializer),
242 Self::Bar(x) => x.serialize(serializer),
243 Self::MarkPrice(x) => x.serialize(serializer),
244 Self::IndexPrice(x) => x.serialize(serializer),
245 Self::FundingRate(x) => x.serialize(serializer),
246 Self::OptionGreeks(x) => x.serialize(serializer),
247 Self::InstrumentStatus(x) => x.serialize(serializer),
248 Self::InstrumentClose(x) => x.serialize(serializer),
249 Self::Custom(x) => x.serialize(serializer),
250 #[cfg(feature = "defi")]
251 Self::Defi(_) => Err(serde::ser::Error::custom(
252 "Data::Defi serialization is not supported",
253 )),
254 }
255 }
256}
257
258macro_rules! impl_try_from_data {
259 ($variant:ident, $type:ty) => {
260 impl TryFrom<Data> for $type {
261 type Error = ();
262
263 fn try_from(value: Data) -> Result<Self, Self::Error> {
264 match value {
265 Data::$variant(x) => Ok(x),
266 _ => Err(()),
267 }
268 }
269 }
270 };
271}
272
273impl TryFrom<Data> for OrderBookDepth10 {
274 type Error = ();
275
276 fn try_from(value: Data) -> Result<Self, Self::Error> {
277 match value {
278 Data::Depth10(x) => Ok(*x),
279 _ => Err(()),
280 }
281 }
282}
283
284impl TryFrom<Data> for OrderBookDeltas {
285 type Error = ();
286
287 fn try_from(value: Data) -> Result<Self, Self::Error> {
288 match value {
289 Data::Deltas(x) => Ok(*x),
290 _ => Err(()),
291 }
292 }
293}
294
295impl_try_from_data!(Quote, QuoteTick);
296impl_try_from_data!(Delta, OrderBookDelta);
297impl_try_from_data!(Trade, TradeTick);
298impl_try_from_data!(Bar, Bar);
299impl_try_from_data!(MarkPrice, MarkPriceUpdate);
300impl_try_from_data!(IndexPrice, IndexPriceUpdate);
301impl_try_from_data!(FundingRate, FundingRateUpdate);
302impl_try_from_data!(OptionGreeks, OptionGreeks);
303impl_try_from_data!(InstrumentStatus, InstrumentStatus);
304impl_try_from_data!(InstrumentClose, InstrumentClose);
305
306#[must_use]
311pub fn to_variant<T: TryFrom<Data>>(data: Vec<Data>) -> Vec<T> {
312 data.into_iter()
313 .filter_map(|d| T::try_from(d).ok())
314 .collect()
315}
316
317impl Data {
318 #[must_use]
320 pub fn instrument_id(&self) -> InstrumentId {
321 match self {
322 Self::Delta(delta) => delta.instrument_id,
323 Self::Deltas(deltas) => deltas.instrument_id,
324 Self::Depth10(depth) => depth.instrument_id,
325 Self::Quote(quote) => quote.instrument_id,
326 Self::Trade(trade) => trade.instrument_id,
327 Self::Bar(bar) => bar.bar_type.instrument_id(),
328 Self::MarkPrice(mark_price) => mark_price.instrument_id,
329 Self::IndexPrice(index_price) => index_price.instrument_id,
330 Self::FundingRate(funding_rate) => funding_rate.instrument_id,
331 Self::OptionGreeks(greeks) => greeks.instrument_id,
332 Self::InstrumentStatus(status) => status.instrument_id,
333 Self::InstrumentClose(close) => close.instrument_id,
334 Self::Custom(custom) => custom
335 .data_type
336 .identifier()
337 .and_then(|s| InstrumentId::from_str(s).ok())
338 .or_else(|| {
339 custom
340 .data_type
341 .metadata()
342 .and_then(|m| m.get_str("instrument_id"))
343 .and_then(|s| InstrumentId::from_str(s).ok())
344 })
345 .unwrap_or_else(|| InstrumentId::from("NULL.NULL")),
346 #[cfg(feature = "defi")]
347 Self::Defi(defi) => defi.instrument_id(),
348 }
349 }
350
351 #[must_use]
353 pub fn is_order_book_data(&self) -> bool {
354 matches!(self, Self::Delta(_) | Self::Deltas(_) | Self::Depth10(_))
355 }
356}
357
358pub trait HasTsInit {
364 fn ts_init(&self) -> UnixNanos;
366}
367
368pub trait CatalogPathPrefix {
370 fn path_prefix() -> &'static str;
372}
373
374#[macro_export]
384macro_rules! impl_catalog_path_prefix {
385 ($type:ty, $path:expr) => {
386 impl $crate::data::CatalogPathPrefix for $type {
387 fn path_prefix() -> &'static str {
388 $path
389 }
390 }
391 };
392}
393
394impl_catalog_path_prefix!(QuoteTick, "quotes");
396impl_catalog_path_prefix!(TradeTick, "trades");
397impl_catalog_path_prefix!(OrderBookDelta, "order_book_deltas");
398impl_catalog_path_prefix!(OrderBookDepth10, "order_book_depths");
399impl_catalog_path_prefix!(Bar, "bars");
400impl_catalog_path_prefix!(IndexPriceUpdate, "index_prices");
401impl_catalog_path_prefix!(MarkPriceUpdate, "mark_prices");
402impl_catalog_path_prefix!(FundingRateUpdate, "funding_rate_update");
403impl_catalog_path_prefix!(OptionGreeks, "option_greeks");
404impl_catalog_path_prefix!(InstrumentStatus, "instrument_status");
405impl_catalog_path_prefix!(InstrumentClose, "instrument_closes");
406
407use crate::instruments::InstrumentAny;
408impl_catalog_path_prefix!(InstrumentAny, "instruments");
409
410impl HasTsInit for Data {
411 fn ts_init(&self) -> UnixNanos {
412 match self {
413 Self::Delta(d) => d.ts_init,
414 Self::Deltas(d) => d.ts_init,
415 Self::Depth10(d) => d.ts_init,
416 Self::Quote(q) => q.ts_init,
417 Self::Trade(t) => t.ts_init,
418 Self::Bar(b) => b.ts_init,
419 Self::MarkPrice(p) => p.ts_init,
420 Self::IndexPrice(p) => p.ts_init,
421 Self::FundingRate(f) => f.ts_init,
422 Self::OptionGreeks(g) => g.ts_init,
423 Self::InstrumentStatus(s) => s.ts_init,
424 Self::InstrumentClose(c) => c.ts_init,
425 Self::Custom(c) => c.data.ts_init(),
426 #[cfg(feature = "defi")]
427 Self::Defi(d) => d.ts_init(),
428 }
429 }
430}
431
432pub fn is_monotonically_increasing_by_init<T: HasTsInit>(data: &[T]) -> bool {
436 data.array_windows()
437 .all(|[a, b]| a.ts_init() <= b.ts_init())
438}
439
440impl From<OrderBookDelta> for Data {
441 fn from(value: OrderBookDelta) -> Self {
442 Self::Delta(value)
443 }
444}
445
446impl From<OrderBookDeltas> for Data {
447 fn from(value: OrderBookDeltas) -> Self {
448 Self::Deltas(Box::new(value))
449 }
450}
451
452impl From<OrderBookDepth10> for Data {
453 fn from(value: OrderBookDepth10) -> Self {
454 Self::Depth10(Box::new(value))
455 }
456}
457
458impl From<QuoteTick> for Data {
459 fn from(value: QuoteTick) -> Self {
460 Self::Quote(value)
461 }
462}
463
464impl From<TradeTick> for Data {
465 fn from(value: TradeTick) -> Self {
466 Self::Trade(value)
467 }
468}
469
470impl From<Bar> for Data {
471 fn from(value: Bar) -> Self {
472 Self::Bar(value)
473 }
474}
475
476impl From<MarkPriceUpdate> for Data {
477 fn from(value: MarkPriceUpdate) -> Self {
478 Self::MarkPrice(value)
479 }
480}
481
482impl From<IndexPriceUpdate> for Data {
483 fn from(value: IndexPriceUpdate) -> Self {
484 Self::IndexPrice(value)
485 }
486}
487
488impl From<FundingRateUpdate> for Data {
489 fn from(value: FundingRateUpdate) -> Self {
490 Self::FundingRate(value)
491 }
492}
493
494impl From<OptionGreeks> for Data {
495 fn from(value: OptionGreeks) -> Self {
496 Self::OptionGreeks(value)
497 }
498}
499
500impl From<InstrumentStatus> for Data {
501 fn from(value: InstrumentStatus) -> Self {
502 Self::InstrumentStatus(value)
503 }
504}
505
506impl From<InstrumentClose> for Data {
507 fn from(value: InstrumentClose) -> Self {
508 Self::InstrumentClose(value)
509 }
510}
511
512#[cfg(feature = "defi")]
513impl From<DefiData> for Data {
514 fn from(value: DefiData) -> Self {
515 Self::Defi(Box::new(value))
516 }
517}
518
519fn value_to_topic_string(v: &JsonValue) -> String {
521 if let Some(s) = v.as_str() {
522 return s.to_string();
523 }
524
525 if let Some(n) = v.as_u64() {
526 return n.to_string();
527 }
528
529 if let Some(n) = v.as_i64() {
530 return n.to_string();
531 }
532
533 if let Some(b) = v.as_bool() {
534 return b.to_string();
535 }
536
537 if let Some(f) = v.as_f64() {
538 return f.to_string();
539 }
540
541 if v.is_null() {
542 return "null".to_string();
543 }
544 serde_json::to_string(v).unwrap_or_default()
545}
546
547fn params_to_topic_suffix(params: &Params) -> String {
549 let mut entries = params.iter().collect::<Vec<_>>();
550 entries.sort_by_key(|(key, _)| *key);
551
552 entries
553 .into_iter()
554 .map(|(k, v)| format!("{k}={}", value_to_topic_string(v)))
555 .collect::<Vec<_>>()
556 .join(".")
557}
558
559#[derive(Clone, Serialize)]
561#[cfg_attr(
562 feature = "python",
563 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
564)]
565#[cfg_attr(
566 feature = "python",
567 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
568)]
569pub struct DataType {
570 type_name: String,
571 metadata: Option<Params>,
572 topic: String,
573 hash: u64,
574 identifier: Option<String>,
575}
576
577impl<'de> Deserialize<'de> for DataType {
578 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
579 where
580 D: serde::Deserializer<'de>,
581 {
582 const FIELDS: &[&str] = &["type_name", "metadata", "topic", "hash", "identifier"];
583
584 #[derive(Deserialize)]
585 #[serde(field_identifier, rename_all = "snake_case")]
586 enum Field {
587 TypeName,
588 Metadata,
589 Topic,
590 Hash,
591 Identifier,
592 #[serde(other)]
593 Other,
594 }
595
596 fn finish(
597 type_name: &str,
598 metadata: Option<Params>,
599 topic: Option<String>,
600 identifier: Option<String>,
601 ) -> DataType {
602 let mut data_type = DataType::new(type_name, metadata, identifier);
603
604 if let Some(topic) = topic {
605 let mut hasher = std::collections::hash_map::DefaultHasher::new();
606 topic.hash(&mut hasher);
607 data_type.topic = topic;
608 data_type.hash = hasher.finish();
609 }
610
611 data_type
612 }
613
614 struct DataTypeVisitor;
615
616 impl<'de> Visitor<'de> for DataTypeVisitor {
617 type Value = DataType;
618
619 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 formatter.write_str("struct DataType")
621 }
622
623 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
624 where
625 A: SeqAccess<'de>,
626 {
627 let type_name: String = seq
628 .next_element()?
629 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
630 let metadata = seq
634 .next_element()?
635 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
636 let topic = seq
637 .next_element()?
638 .ok_or_else(|| de::Error::invalid_length(2, &self))?;
639 let _hash: u64 = seq
640 .next_element()?
641 .ok_or_else(|| de::Error::invalid_length(3, &self))?;
642 let identifier = seq
643 .next_element()?
644 .ok_or_else(|| de::Error::invalid_length(4, &self))?;
645
646 Ok(finish(&type_name, metadata, Some(topic), identifier))
647 }
648
649 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
650 where
651 A: MapAccess<'de>,
652 {
653 let mut type_name: Option<String> = None;
654 let mut metadata = None;
655 let mut topic = None;
656 let mut hash_seen = false;
657 let mut identifier = None;
658
659 while let Some(key) = map.next_key()? {
660 match key {
661 Field::TypeName => {
662 if type_name.is_some() {
663 return Err(de::Error::duplicate_field("type_name"));
664 }
665 type_name = Some(map.next_value()?);
666 }
667 Field::Metadata => {
668 if metadata.is_some() {
669 return Err(de::Error::duplicate_field("metadata"));
670 }
671 metadata = Some(map.next_value()?);
672 }
673 Field::Topic => {
674 if topic.is_some() {
675 return Err(de::Error::duplicate_field("topic"));
676 }
677 topic = Some(map.next_value()?);
678 }
679 Field::Hash => {
680 if hash_seen {
681 return Err(de::Error::duplicate_field("hash"));
682 }
683 hash_seen = true;
684 let _: Option<u64> = map.next_value()?;
685 }
686 Field::Identifier => {
687 if identifier.is_some() {
688 return Err(de::Error::duplicate_field("identifier"));
689 }
690 identifier = Some(map.next_value()?);
691 }
692 Field::Other => {
693 let _: IgnoredAny = map.next_value()?;
694 }
695 }
696 }
697
698 let type_name = type_name.ok_or_else(|| de::Error::missing_field("type_name"))?;
699 Ok(finish(
700 &type_name,
701 metadata.unwrap_or(None),
702 topic.unwrap_or(None),
703 identifier.unwrap_or(None),
704 ))
705 }
706 }
707
708 deserializer.deserialize_struct("DataType", FIELDS, DataTypeVisitor)
709 }
710}
711
712impl DataType {
713 #[must_use]
715 pub fn new(type_name: &str, metadata: Option<Params>, identifier: Option<String>) -> Self {
716 let topic = if let Some(ref meta) = metadata {
718 if meta.is_empty() {
719 type_name.to_string()
720 } else {
721 format!("{type_name}.{}", params_to_topic_suffix(meta))
722 }
723 } else {
724 type_name.to_string()
725 };
726
727 let mut hasher = std::collections::hash_map::DefaultHasher::new();
729 topic.hash(&mut hasher);
730
731 Self {
732 type_name: type_name.to_owned(),
733 metadata,
734 topic,
735 hash: hasher.finish(),
736 identifier,
737 }
738 }
739
740 #[must_use]
744 pub fn from_parts(type_name: &str, topic: &str, metadata: Option<Params>) -> Self {
745 let mut hasher = std::collections::hash_map::DefaultHasher::new();
746 topic.hash(&mut hasher);
747 Self {
748 type_name: type_name.to_owned(),
749 metadata,
750 topic: topic.to_owned(),
751 hash: hasher.finish(),
752 identifier: None,
753 }
754 }
755
756 pub fn to_persistence_json(&self) -> Result<String, serde_json::Error> {
762 let mut map = serde_json::Map::new();
763 map.insert(
764 "type_name".to_string(),
765 serde_json::Value::String(self.type_name.clone()),
766 );
767 map.insert(
768 "metadata".to_string(),
769 self.metadata.as_ref().map_or(serde_json::Value::Null, |m| {
770 serde_json::to_value(m).unwrap_or(serde_json::Value::Null)
771 }),
772 );
773
774 if let Some(ref id) = self.identifier {
775 map.insert(
776 "identifier".to_string(),
777 serde_json::Value::String(id.clone()),
778 );
779 }
780 serde_json::to_string(&serde_json::Value::Object(map))
781 }
782
783 pub fn from_persistence_json(s: &str) -> Result<Self, anyhow::Error> {
790 let value: serde_json::Value =
791 serde_json::from_str(s).map_err(|e| anyhow::anyhow!("Invalid data_type JSON: {e}"))?;
792 let obj = value
793 .as_object()
794 .ok_or_else(|| anyhow::anyhow!("data_type must be a JSON object"))?;
795 let type_name = obj
796 .get("type_name")
797 .and_then(|v| v.as_str())
798 .ok_or_else(|| anyhow::anyhow!("data_type must have type_name"))?
799 .to_string();
800 let metadata = obj.get("metadata").and_then(|m| {
801 if m.is_null() {
802 None
803 } else {
804 let p: Params = serde_json::from_value(m.clone()).ok()?;
805 if p.is_empty() { None } else { Some(p) }
806 }
807 });
808 let identifier = obj
809 .get("identifier")
810 .and_then(|v| v.as_str())
811 .map(String::from);
812 Ok(Self::new(&type_name, metadata, identifier))
813 }
814
815 #[must_use]
817 pub fn type_name(&self) -> &str {
818 self.type_name.as_str()
819 }
820
821 #[must_use]
823 pub fn metadata(&self) -> Option<&Params> {
824 self.metadata.as_ref()
825 }
826
827 #[must_use]
829 pub fn metadata_str(&self) -> String {
830 self.metadata.as_ref().map_or_else(
831 || "null".to_string(),
832 |metadata| {
833 let mut entries = metadata.iter().collect::<Vec<_>>();
834 entries.sort_by_key(|(key, _)| *key);
835
836 let mut metadata_map = serde_json::Map::new();
837 for (key, value) in entries {
838 metadata_map.insert(key.clone(), value.clone());
839 }
840
841 serde_json::to_string(&metadata_map).unwrap_or_default()
842 },
843 )
844 }
845
846 #[must_use]
848 pub fn metadata_string_map(&self) -> Option<std::collections::HashMap<String, String>> {
849 self.metadata.as_ref().map(|p| {
850 p.iter()
851 .map(|(k, v)| (k.clone(), value_to_topic_string(v)))
852 .collect()
853 })
854 }
855
856 #[must_use]
858 pub fn precomputed_hash(&self) -> u64 {
859 self.hash
860 }
861
862 #[must_use]
864 pub fn topic(&self) -> &str {
865 self.topic.as_str()
866 }
867
868 #[must_use]
870 pub fn identifier(&self) -> Option<&str> {
871 self.identifier.as_deref()
872 }
873
874 #[must_use]
881 pub fn instrument_id(&self) -> Option<InstrumentId> {
882 let metadata = self.metadata.as_ref()?;
883 let instrument_id = metadata.get_str("instrument_id")?;
884 Some(
885 InstrumentId::from_str(instrument_id)
886 .expect("Invalid `InstrumentId` for 'instrument_id'"),
887 )
888 }
889
890 #[must_use]
897 pub fn venue(&self) -> Option<Venue> {
898 let metadata = self.metadata.as_ref()?;
899 let venue_str = metadata.get_str("venue")?;
900 Some(Venue::from(venue_str))
901 }
902
903 #[must_use]
910 pub fn start(&self) -> Option<UnixNanos> {
911 let metadata = self.metadata.as_ref()?;
912 let start_str = metadata.get_str("start")?;
913 Some(UnixNanos::from_str(start_str).expect("Invalid `UnixNanos` for 'start'"))
914 }
915
916 #[must_use]
923 pub fn end(&self) -> Option<UnixNanos> {
924 let metadata = self.metadata.as_ref()?;
925 let end_str = metadata.get_str("end")?;
926 Some(UnixNanos::from_str(end_str).expect("Invalid `UnixNanos` for 'end'"))
927 }
928
929 #[must_use]
936 pub fn limit(&self) -> Option<usize> {
937 let metadata = self.metadata.as_ref()?;
938 metadata.get_usize("limit").or_else(|| {
939 metadata
940 .get_str("limit")
941 .map(|s| s.parse::<usize>().expect("Invalid `usize` for 'limit'"))
942 })
943 }
944}
945
946impl PartialEq for DataType {
947 fn eq(&self, other: &Self) -> bool {
948 self.topic == other.topic
949 }
950}
951
952impl Eq for DataType {}
953
954impl PartialOrd for DataType {
955 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
956 Some(self.cmp(other))
957 }
958}
959
960impl Ord for DataType {
961 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
962 self.topic.cmp(&other.topic)
963 }
964}
965
966impl Hash for DataType {
967 fn hash<H: Hasher>(&self, state: &mut H) {
968 self.hash.hash(state);
969 }
970}
971
972impl Display for DataType {
973 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
974 write!(f, "{}", self.topic)
975 }
976}
977
978impl Debug for DataType {
979 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
980 write!(
981 f,
982 "DataType(type_name={}, metadata={:?}, identifier={:?})",
983 self.type_name, self.metadata, self.identifier
984 )
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use std::hash::DefaultHasher;
991
992 use rstest::*;
993 use serde_json::json;
994
995 use super::*;
996
997 fn params_from_json(value: serde_json::Value) -> Params {
998 serde_json::from_value(value).expect("valid Params JSON")
999 }
1000
1001 fn hash_data_type(data_type: &DataType) -> u64 {
1002 let mut hasher = DefaultHasher::new();
1003 data_type.hash(&mut hasher);
1004 hasher.finish()
1005 }
1006
1007 #[rstest]
1008 fn test_data_type_creation_with_metadata() {
1009 let metadata = Some(params_from_json(
1010 json!({"key1": "value1", "key2": "value2"}),
1011 ));
1012 let data_type = DataType::new("ExampleType", metadata.clone(), None);
1013
1014 assert_eq!(data_type.type_name(), "ExampleType");
1015 assert_eq!(data_type.topic(), "ExampleType.key1=value1.key2=value2");
1016 assert_eq!(data_type.metadata(), metadata.as_ref());
1017 }
1018
1019 #[rstest]
1020 fn test_data_type_topic_identity_uses_canonical_metadata_order() {
1021 let mut metadata1 = Params::new();
1022 metadata1.insert("b".to_string(), json!(2));
1023 metadata1.insert("a".to_string(), json!(1));
1024 let mut metadata2 = Params::new();
1025 metadata2.insert("a".to_string(), json!(1));
1026 metadata2.insert("b".to_string(), json!(2));
1027
1028 let data_type1 = DataType::new("ExampleType", Some(metadata1), None);
1029 let data_type2 = DataType::new("ExampleType", Some(metadata2), None);
1030 let mut hasher1 = DefaultHasher::new();
1031 data_type1.hash(&mut hasher1);
1032 let hash1 = hasher1.finish();
1033 let mut hasher2 = DefaultHasher::new();
1034 data_type2.hash(&mut hasher2);
1035 let hash2 = hasher2.finish();
1036
1037 assert_eq!(data_type1.topic(), "ExampleType.a=1.b=2");
1038 assert_eq!(data_type1.topic(), data_type2.topic());
1039 assert_eq!(data_type1, data_type2);
1040 assert_eq!(hash1, hash2);
1041 assert_eq!(format!("{data_type1}"), format!("{data_type2}"));
1042 assert_eq!(data_type1.metadata_str(), r#"{"a":1,"b":2}"#);
1043 assert_eq!(data_type1.metadata_str(), data_type2.metadata_str());
1044 }
1045
1046 #[rstest]
1047 fn test_data_type_creation_without_metadata() {
1048 let data_type = DataType::new("ExampleType", None, None);
1049
1050 assert_eq!(data_type.type_name(), "ExampleType");
1051 assert_eq!(data_type.topic(), "ExampleType");
1052 assert_eq!(data_type.metadata(), None);
1053 }
1054
1055 #[rstest]
1056 fn test_data_type_equality() {
1057 let metadata1 = Some(params_from_json(json!({"key1": "value1"})));
1058 let metadata2 = Some(params_from_json(json!({"key1": "value1"})));
1059
1060 let data_type1 = DataType::new("ExampleType", metadata1, None);
1061 let data_type2 = DataType::new("ExampleType", metadata2, None);
1062
1063 assert_eq!(data_type1, data_type2);
1064 }
1065
1066 #[rstest]
1067 fn test_data_type_inequality() {
1068 let metadata1 = Some(params_from_json(json!({"key1": "value1"})));
1069 let metadata2 = Some(params_from_json(json!({"key2": "value2"})));
1070
1071 let data_type1 = DataType::new("ExampleType", metadata1, None);
1072 let data_type2 = DataType::new("ExampleType", metadata2, None);
1073
1074 assert_ne!(data_type1, data_type2);
1075 }
1076
1077 #[rstest]
1078 fn test_data_type_ordering() {
1079 let metadata1 = Some(params_from_json(json!({"key1": "value1"})));
1080 let metadata2 = Some(params_from_json(json!({"key2": "value2"})));
1081
1082 let data_type1 = DataType::new("ExampleTypeA", metadata1, None);
1083 let data_type2 = DataType::new("ExampleTypeB", metadata2, None);
1084
1085 assert!(data_type1 < data_type2);
1086 }
1087
1088 #[rstest]
1089 fn test_data_type_hash() {
1090 let metadata = Some(params_from_json(json!({"key1": "value1"})));
1091
1092 let data_type1 = DataType::new("ExampleType", metadata.clone(), None);
1093 let data_type2 = DataType::new("ExampleType", metadata, None);
1094
1095 let mut hasher1 = DefaultHasher::new();
1096 data_type1.hash(&mut hasher1);
1097 let hash1 = hasher1.finish();
1098
1099 let mut hasher2 = DefaultHasher::new();
1100 data_type2.hash(&mut hasher2);
1101 let hash2 = hasher2.finish();
1102
1103 assert_eq!(hash1, hash2);
1104 }
1105
1106 #[rstest]
1107 fn test_data_type_deserialization_recomputes_hash_from_topic() {
1108 let expected = DataType::from_parts(
1109 "ExampleType",
1110 "custom.topic",
1111 Some(params_from_json(json!({"key": "value"}))),
1112 );
1113 let payload = json!({
1114 "type_name": expected.type_name(),
1115 "metadata": expected.metadata(),
1116 "topic": expected.topic(),
1117 "hash": expected.precomputed_hash() ^ u64::MAX,
1118 "identifier": "catalog/path",
1119 });
1120
1121 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1122
1123 assert_eq!(deserialized.topic(), expected.topic());
1124 assert_eq!(deserialized.precomputed_hash(), expected.precomputed_hash());
1125 }
1126
1127 #[rstest]
1128 fn test_data_type_deserialization_without_cache_fields_uses_constructor() {
1129 let payload = json!({
1130 "type_name": "ExampleType",
1131 "metadata": {"z": 9, "a": 1},
1132 "identifier": "catalog/path",
1133 });
1134 let expected = DataType::new(
1135 "ExampleType",
1136 Some(params_from_json(json!({"z": 9, "a": 1}))),
1137 Some("catalog/path".to_string()),
1138 );
1139
1140 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1141
1142 assert_eq!(deserialized.topic(), expected.topic());
1143 assert_eq!(deserialized.precomputed_hash(), expected.precomputed_hash());
1144 }
1145
1146 #[rstest]
1147 fn test_data_type_deserialization_preserves_topic_without_hash() {
1148 let expected = DataType::from_parts("ExampleType", "custom.topic", None);
1149 let payload = json!({
1150 "type_name": "ExampleType",
1151 "metadata": null,
1152 "topic": "custom.topic",
1153 });
1154
1155 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1156
1157 assert_eq!(deserialized.topic(), "custom.topic");
1158 assert_eq!(deserialized.precomputed_hash(), expected.precomputed_hash());
1159 }
1160
1161 #[rstest]
1162 fn test_data_type_deserialization_ignores_hash_without_topic() {
1163 let expected = DataType::new("ExampleType", None, None);
1164 let payload = json!({
1165 "type_name": "ExampleType",
1166 "metadata": null,
1167 "hash": expected.precomputed_hash() ^ u64::MAX,
1168 });
1169
1170 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1171
1172 assert_eq!(deserialized.topic(), expected.topic());
1173 assert_eq!(deserialized.precomputed_hash(), expected.precomputed_hash());
1174 }
1175
1176 #[rstest]
1177 fn test_data_type_deserialization_rejects_duplicate_map_key() {
1178 let payload = r#"{"type_name":"ExampleType","topic":"first","topic":"second"}"#;
1179
1180 let error = serde_json::from_str::<DataType>(payload).unwrap_err();
1181
1182 assert!(error.to_string().contains("duplicate field `topic`"));
1183 }
1184
1185 #[rstest]
1186 #[case(
1187 r#"{"type_name":"ExampleType","topic":null,"topic":"second"}"#,
1188 "duplicate field `topic`"
1189 )]
1190 #[case(
1191 r#"{"type_name":"ExampleType","hash":null,"hash":7}"#,
1192 "duplicate field `hash`"
1193 )]
1194 #[case(
1195 r#"{"type_name":"ExampleType","metadata":null,"metadata":{"a":1}}"#,
1196 "duplicate field `metadata`"
1197 )]
1198 #[case(
1199 r#"{"type_name":"ExampleType","identifier":null,"identifier":"second"}"#,
1200 "duplicate field `identifier`"
1201 )]
1202 fn test_data_type_deserialization_rejects_duplicate_map_key_after_null(
1203 #[case] payload: &str,
1204 #[case] expected: &str,
1205 ) {
1206 let error = serde_json::from_str::<DataType>(payload).unwrap_err();
1209
1210 assert!(error.to_string().contains(expected));
1211 }
1212
1213 #[rstest]
1214 fn test_data_type_serde_roundtrip_preserves_fields_and_repairs_hash() {
1215 let expected = DataType::from_parts(
1216 "ExampleType",
1217 "custom.topic",
1218 Some(params_from_json(json!({"key": "value"}))),
1219 );
1220 let payload = json!({
1221 "type_name": expected.type_name(),
1222 "metadata": expected.metadata(),
1223 "topic": expected.topic(),
1224 "hash": expected.precomputed_hash() ^ u64::MAX,
1225 "identifier": "catalog/path",
1226 });
1227 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1228
1229 let json = serde_json::to_string(&deserialized).unwrap();
1230 let roundtripped: DataType = serde_json::from_str(&json).unwrap();
1231
1232 assert_eq!(roundtripped.type_name(), "ExampleType");
1233 assert_eq!(roundtripped.metadata(), expected.metadata());
1234 assert_eq!(roundtripped.identifier(), Some("catalog/path"));
1235 assert_eq!(roundtripped.topic(), "custom.topic");
1236 assert_eq!(roundtripped.precomputed_hash(), expected.precomputed_hash());
1237 }
1238
1239 #[rstest]
1240 fn test_data_type_serialized_cache_fields_remain_wire_compatible() {
1241 #[derive(Deserialize)]
1242 struct LegacyDataType {
1243 type_name: String,
1244 metadata: Option<Params>,
1245 topic: String,
1246 hash: u64,
1247 identifier: Option<String>,
1248 }
1249
1250 let expected = DataType::new(
1251 "ExampleType",
1252 Some(params_from_json(json!({"key": "value"}))),
1253 Some("catalog/path".to_string()),
1254 );
1255 let mut payload = serde_json::to_value(&expected).unwrap();
1256 payload["hash"] = json!(expected.precomputed_hash() ^ u64::MAX);
1257 let repaired: DataType = serde_json::from_value(payload).unwrap();
1258
1259 let serialized = serde_json::to_value(&repaired).unwrap();
1260 assert!(serialized.get("topic").is_some());
1261 assert!(serialized.get("hash").is_some());
1262
1263 let legacy: LegacyDataType = serde_json::from_value(serialized).unwrap();
1264 assert_eq!(legacy.type_name, expected.type_name());
1265 assert_eq!(legacy.metadata.as_ref(), expected.metadata());
1266 assert_eq!(legacy.topic, expected.topic());
1267 assert_eq!(legacy.hash, expected.precomputed_hash());
1268 assert_eq!(legacy.identifier.as_deref(), expected.identifier());
1269 }
1270
1271 #[rstest]
1272 fn test_data_type_display() {
1273 let metadata = Some(params_from_json(json!({"key1": "value1"})));
1274 let data_type = DataType::new("ExampleType", metadata, None);
1275
1276 assert_eq!(format!("{data_type}"), "ExampleType.key1=value1");
1277 }
1278
1279 #[rstest]
1280 fn test_data_type_debug() {
1281 let metadata = Some(params_from_json(json!({"key1": "value1"})));
1282 let data_type = DataType::new("ExampleType", metadata.clone(), None);
1283
1284 assert_eq!(
1285 format!("{data_type:?}"),
1286 format!("DataType(type_name=ExampleType, metadata={metadata:?}, identifier=None)")
1287 );
1288 }
1289
1290 #[rstest]
1291 fn test_parse_instrument_id_from_metadata() {
1292 let instrument_id_str = "MSFT.XNAS";
1293 let metadata = Some(params_from_json(
1294 json!({"instrument_id": instrument_id_str}),
1295 ));
1296 let data_type = DataType::new("InstrumentAny", metadata, None);
1297
1298 assert_eq!(
1299 data_type.instrument_id().unwrap(),
1300 InstrumentId::from_str(instrument_id_str).unwrap()
1301 );
1302 }
1303
1304 #[rstest]
1305 fn test_parse_venue_from_metadata() {
1306 let venue_str = "BINANCE";
1307 let metadata = Some(params_from_json(json!({"venue": venue_str})));
1308 let data_type = DataType::new(stringify!(InstrumentAny), metadata, None);
1309
1310 assert_eq!(data_type.venue().unwrap(), Venue::new(venue_str));
1311 }
1312
1313 #[rstest]
1314 fn test_parse_start_from_metadata() {
1315 let start_ns = 1_600_054_595_844_758_000;
1316 let metadata = Some(params_from_json(json!({"start": start_ns.to_string()})));
1317 let data_type = DataType::new(stringify!(TradeTick), metadata, None);
1318
1319 assert_eq!(data_type.start().unwrap(), UnixNanos::from(start_ns),);
1320 }
1321
1322 #[rstest]
1323 fn test_parse_end_from_metadata() {
1324 let end_ns = 1_720_954_595_844_758_000;
1325 let metadata = Some(params_from_json(json!({"end": end_ns.to_string()})));
1326 let data_type = DataType::new(stringify!(TradeTick), metadata, None);
1327
1328 assert_eq!(data_type.end().unwrap(), UnixNanos::from(end_ns),);
1329 }
1330
1331 #[rstest]
1332 fn test_parse_limit_from_metadata() {
1333 let limit = 1000;
1334 let metadata = Some(params_from_json(json!({"limit": limit})));
1335 let data_type = DataType::new(stringify!(TradeTick), metadata, None);
1336
1337 assert_eq!(data_type.limit().unwrap(), limit);
1338 }
1339
1340 #[rstest]
1341 fn test_data_type_metadata_accessors_return_none_without_metadata() {
1342 let data_type = DataType::new(stringify!(TradeTick), None, None);
1343
1344 assert_eq!(data_type.instrument_id(), None);
1345 assert_eq!(data_type.venue(), None);
1346 assert_eq!(data_type.start(), None);
1347 assert_eq!(data_type.end(), None);
1348 }
1349
1350 #[rstest]
1351 fn test_data_type_persistence_json_with_identifier() {
1352 let data_type = DataType::new("MyCustomType", None, Some("venue//symbol".to_string()));
1353 let json = data_type.to_persistence_json().unwrap();
1354 assert!(!json.contains("topic"));
1355 assert!(json.contains("\"identifier\":\"venue//symbol\""));
1356 let restored = DataType::from_persistence_json(&json).unwrap();
1357 assert_eq!(restored.type_name(), "MyCustomType");
1358 assert_eq!(restored.identifier(), Some("venue//symbol"));
1359 assert_eq!(restored.topic(), "MyCustomType");
1360 }
1361
1362 #[rstest]
1363 fn test_data_type_from_persistence_json_rebuilds_canonical_topic() {
1364 let json = r#"{
1365 "type_name": "ExampleType",
1366 "topic": "ExampleType.z=9.a=1",
1367 "metadata": {"z": 9, "a": 1}
1368 }"#;
1369
1370 let restored = DataType::from_persistence_json(json).unwrap();
1371
1372 assert_eq!(restored.topic(), "ExampleType.a=1.z=9");
1373 }
1374
1375 #[rstest]
1376 fn test_data_type_persistence_result_hashes_like_equal_deserialized_value() {
1377 let persistence_json = r#"{
1378 "type_name": "ExampleType",
1379 "topic": "ignored.legacy.topic",
1380 "metadata": {"z": 9, "a": 1},
1381 "identifier": "catalog/path"
1382 }"#;
1383 let persisted = DataType::from_persistence_json(persistence_json).unwrap();
1384 let payload = json!({
1385 "type_name": persisted.type_name(),
1386 "metadata": persisted.metadata(),
1387 "topic": persisted.topic(),
1388 "hash": persisted.precomputed_hash() ^ u64::MAX,
1389 "identifier": persisted.identifier(),
1390 });
1391 let deserialized: DataType = serde_json::from_value(payload).unwrap();
1392
1393 assert_eq!(persisted.topic(), "ExampleType.a=1.z=9");
1394 assert_eq!(persisted.identifier(), Some("catalog/path"));
1395 assert_eq!(deserialized, persisted);
1396 assert_eq!(hash_data_type(&deserialized), hash_data_type(&persisted));
1397 }
1398
1399 #[rstest]
1400 fn test_data_type_identifier_getter() {
1401 let data_type = DataType::new("T", None, Some("id".to_string()));
1402 assert_eq!(data_type.identifier(), Some("id"));
1403 let data_type_no_id = DataType::new("T", None, None);
1404 assert_eq!(data_type_no_id.identifier(), None);
1405 }
1406}