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