databento_defs/
record.rs

1//! Market data types for encoding different Databento [`Schema`](crate::enums::Schema)s and conversion functions.
2use std::{mem, ops::RangeInclusive, os::raw::c_char, ptr::NonNull};
3
4use crate::enums::SecurityUpdateAction;
5
6/// Common data for all Databento records.
7#[repr(C)]
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[cfg_attr(feature = "trivial_copy", derive(Copy))]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11pub struct RecordHeader {
12    /// The length of the message in 32-bit words.
13    #[cfg_attr(feature = "serde", serde(skip))]
14    pub length: u8,
15    /// The record type; with `0x00..0x0F` specifying booklevel size. Record
16    /// types implement the trait [`ConstTypeId`], which contains a constant
17    /// ID specific to that record type.
18    pub rtype: u8,
19    /// The publisher ID assigned by Databento.
20    pub publisher_id: u16,
21    /// The product ID assigned by the venue.
22    pub product_id: u32,
23    /// The matching engine received timestamp expressed as number of nanoseconds since UNIX epoch.
24    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
25    pub ts_event: u64,
26}
27
28pub const TICK_MSG_TYPE_ID: u8 = 0xA0;
29/// Market-by-order (MBO) tick message.
30/// `hd.rtype = 0xA0`
31#[repr(C)]
32#[derive(Clone, Debug, PartialEq, Eq)]
33#[cfg_attr(feature = "trivial_copy", derive(Copy))]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35pub struct MboMsg {
36    /// The common header.
37    pub hd: RecordHeader,
38    /// The order ID assigned at the venue.
39    pub order_id: u64,
40    /// The order price expressed as a signed integer where every 1 unit
41    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
42    pub price: i64,
43    /// The order quantity.
44    pub size: u32,
45    /// A combination of packet end with matching engine status.
46    pub flags: u8,
47    /// A channel ID within the venue.
48    pub channel_id: u8,
49    /// The event action. Can be M\[odify\], T\[rade\], C\[ancel\], A\[dd\]
50    /// or special: \[S\]tatus, \[U\]pdate.
51    pub action: c_char,
52    /// The order side. Can be A\[sk\], B\[id\] or N\[one\].
53    pub side: c_char,
54    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
55    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
56    pub ts_recv: u64,
57    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
58    pub ts_in_delta: i32,
59    /// The message sequence number assigned at the venue.
60    pub sequence: u32,
61}
62
63// Named `DB_BA` in C
64/// A book level.
65#[repr(C)]
66#[derive(Clone, Debug, PartialEq, Eq)]
67#[cfg_attr(feature = "trivial_copy", derive(Copy))]
68#[cfg_attr(feature = "serde", derive(serde::Serialize))]
69pub struct BidAskPair {
70    /// The bid price.
71    pub bid_px: i64,
72    /// The ask price.
73    pub ask_px: i64,
74    /// The bid size.
75    pub bid_sz: u32,
76    /// The ask size.
77    pub ask_sz: u32,
78    /// The bid order count.
79    pub bid_ct: u32,
80    /// The ask order count.
81    pub ask_ct: u32,
82}
83
84pub const MAX_UA_BOOK_LEVEL: usize = 0xF;
85pub const MBP_MSG_TYPE_ID_RANGE: RangeInclusive<u8> = 0x00..=(MAX_UA_BOOK_LEVEL as u8);
86
87/// Market by price implementation with a book depth of 0. Equivalent to
88/// MBP-0.
89#[repr(C)]
90#[derive(Clone, Debug, PartialEq, Eq)]
91#[cfg_attr(feature = "trivial_copy", derive(Copy))]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct TradeMsg {
94    /// The common header.
95    pub hd: RecordHeader,
96    /// The order price expressed as a signed integer where every 1 unit
97    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
98    pub price: i64,
99    /// The order quantity.
100    pub size: u32,
101    /// The event action. Can be M\[odify\], T\[rade\], C\[ancel\], A\[dd\]
102    /// or special: \[S\]tatus, \[U\]pdate.
103    pub action: c_char,
104    /// The order side. Can be A\[sk\], B\[id\] or N\[one\].
105    pub side: c_char,
106    /// A combination of packet end with matching engine status.
107    pub flags: u8,
108    /// The depth of actual book change.
109    pub depth: u8,
110    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
111    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
112    pub ts_recv: u64,
113    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
114    pub ts_in_delta: i32,
115    /// The message sequence number assigned at the venue.
116    pub sequence: u32,
117    #[cfg_attr(feature = "serde", serde(skip))]
118    pub booklevel: [BidAskPair; 0],
119}
120
121/// Market by price implementation with a known book depth of 1.
122#[repr(C)]
123#[derive(Clone, Debug, PartialEq, Eq)]
124#[cfg_attr(feature = "trivial_copy", derive(Copy))]
125#[cfg_attr(feature = "serde", derive(serde::Serialize))]
126pub struct Mbp1Msg {
127    /// The common header.
128    pub hd: RecordHeader,
129    /// The order price expressed as a signed integer where every 1 unit
130    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
131    pub price: i64,
132    /// The order quantity.
133    pub size: u32,
134    /// The event action. Can be M\[odify\], T\[rade\], C\[ancel\], A\[dd\]
135    /// or special: \[S\]tatus, \[U\]pdate.
136    pub action: c_char,
137    /// The order side. Can be A\[sk\], B\[id\] or N\[one\].
138    pub side: c_char,
139    /// A combination of packet end with matching engine status.
140    pub flags: u8,
141    /// The depth of actual book change.
142    pub depth: u8,
143    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
144    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
145    pub ts_recv: u64,
146    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
147    pub ts_in_delta: i32,
148    /// The message sequence number assigned at the venue.
149    pub sequence: u32,
150    pub booklevel: [BidAskPair; 1],
151}
152
153/// Market by price implementation with a known book depth of 10.
154#[repr(C)]
155#[derive(Clone, Debug, PartialEq, Eq)]
156#[cfg_attr(feature = "trivial_copy", derive(Copy))]
157#[cfg_attr(feature = "serde", derive(serde::Serialize))]
158pub struct Mbp10Msg {
159    /// The common header.
160    pub hd: RecordHeader,
161    /// The order price expressed as a signed integer where every 1 unit
162    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
163    pub price: i64,
164    /// The order quantity.
165    pub size: u32,
166    /// The event action. Can be M\[odify\], T\[rade\], C\[ancel\], A\[dd\]
167    /// or special: \[S\]tatus, \[U\]pdate.
168    pub action: c_char,
169    /// The order side. Can be A\[sk\], B\[id\] or N\[one\].
170    pub side: c_char,
171    /// A combination of packet end with matching engine status.
172    pub flags: u8,
173    /// The depth of actual book change.
174    pub depth: u8,
175    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
176    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
177    pub ts_recv: u64,
178    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
179    pub ts_in_delta: i32,
180    /// The message sequence number assigned at the venue.
181    pub sequence: u32,
182    pub booklevel: [BidAskPair; 10],
183}
184
185pub type TbboMsg = Mbp1Msg;
186
187pub const OHLCV_TYPE_ID: u8 = 0x11;
188/// Open, high, low, close, and volume.
189#[repr(C)]
190#[derive(Clone, Debug, PartialEq, Eq)]
191#[cfg_attr(feature = "trivial_copy", derive(Copy))]
192#[cfg_attr(feature = "serde", derive(serde::Serialize))]
193pub struct OhlcvMsg {
194    /// The common header.
195    pub hd: RecordHeader,
196    /// The open price for the bar.
197    pub open: i64,
198    /// The high price for the bar.
199    pub high: i64,
200    /// The low price for the bar.
201    pub low: i64,
202    /// The close price for the bar.
203    pub close: i64,
204    /// The total volume traded during the aggregation period.
205    pub volume: u64,
206}
207
208pub const STATUS_MSG_TYPE_ID: u8 = 0x12;
209/// Trading status update message
210/// `hd.rtype = 0x12`
211#[repr(C)]
212#[derive(Clone, Debug, PartialEq, Eq)]
213#[cfg_attr(feature = "trivial_copy", derive(Copy))]
214#[cfg_attr(feature = "serde", derive(serde::Serialize))]
215pub struct StatusMsg {
216    /// The common header.
217    pub hd: RecordHeader,
218    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
219    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
220    pub ts_recv: u64,
221    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
222    pub group: [c_char; 21],
223    pub trading_status: u8,
224    pub halt_reason: u8,
225    pub trading_event: u8,
226}
227
228pub const INSTRUMENT_DEF_MSG_TYPE_ID: u8 = 0x13;
229/// Definition of an instrument.
230/// `hd.rtype = 0x13`
231#[repr(C)]
232#[derive(Clone, Debug, PartialEq, Eq)]
233#[cfg_attr(feature = "trivial_copy", derive(Copy))]
234#[cfg_attr(feature = "serde", derive(serde::Serialize))]
235#[doc(hidden)]
236pub struct InstrumentDefMsg {
237    /// The common header.
238    pub hd: RecordHeader,
239    /// The capture server received timestamp expressed as number of nanoseconds since UNIX epoch.
240    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
241    pub ts_recv: u64,
242    pub min_price_increment: i64,
243    pub display_factor: i64,
244    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
245    pub expiration: u64,
246    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
247    pub activation: u64,
248    pub high_limit_price: i64,
249    pub low_limit_price: i64,
250    pub max_price_variation: i64,
251    pub trading_reference_price: i64,
252    pub unit_of_measure_qty: i64,
253    pub min_price_increment_amount: i64,
254    pub price_ratio: i64,
255    pub inst_attrib_value: i32,
256    pub underlying_id: u32,
257    pub cleared_volume: i32,
258    pub market_depth_implied: i32,
259    pub market_depth: i32,
260    pub market_segment_id: u32,
261    pub max_trade_vol: u32,
262    pub min_lot_size: i32,
263    pub min_lot_size_block: i32,
264    pub min_lot_size_round_lot: i32,
265    pub min_trade_vol: u32,
266    pub open_interest_qty: i32,
267    pub contract_multiplier: i32,
268    pub decay_quantity: i32,
269    pub original_contract_size: i32,
270    pub related_security_id: u32,
271    pub trading_reference_date: u16,
272    pub appl_id: i16,
273    pub maturity_year: u16,
274    pub decay_start_date: u16,
275    pub channel_id: u16,
276    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
277    pub currency: [c_char; 4],
278    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
279    pub settl_currency: [c_char; 4],
280    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
281    pub secsubtype: [c_char; 6],
282    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
283    pub symbol: [c_char; 22],
284    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
285    pub group: [c_char; 21],
286    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
287    pub exchange: [c_char; 5],
288    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
289    pub asset: [c_char; 7],
290    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
291    pub cfi: [c_char; 7],
292    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
293    pub security_type: [c_char; 7],
294    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
295    pub unit_of_measure: [c_char; 31],
296    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
297    pub underlying: [c_char; 21],
298    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
299    pub related: [c_char; 21],
300    pub match_algorithm: c_char,
301    pub md_security_trading_status: u8,
302    pub main_fraction: u8,
303    pub price_display_format: u8,
304    pub settl_price_type: u8,
305    pub sub_fraction: u8,
306    pub underlying_product: u8,
307    #[cfg_attr(
308        feature = "serde",
309        serde(serialize_with = "serialize_enum_as_char_repr")
310    )]
311    pub security_update_action: SecurityUpdateAction,
312    pub maturity_month: u8,
313    pub maturity_day: u8,
314    pub maturity_week: u8,
315    pub user_defined_instrument: c_char,
316    pub contract_multiplier_unit: i8,
317    pub flow_schedule_type: i8,
318    pub tick_rule: u8,
319    /// Adjust filler for alignment.
320    #[cfg_attr(feature = "serde", serde(skip))]
321    pub _dummy: [c_char; 3],
322}
323
324pub const IMBALANCE_TYPE_ID: u8 = 0x14;
325/// Order imbalance message.
326#[repr(C)]
327#[derive(Clone, Debug, PartialEq, Eq)]
328#[cfg_attr(feature = "trivial_copy", derive(Copy))]
329#[cfg_attr(feature = "serde", derive(serde::Serialize))]
330#[doc(hidden)]
331pub struct Imbalance {
332    pub hd: RecordHeader,
333    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
334    pub ts_recv: u64,
335    pub ref_price: i64,
336    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_large_u64"))]
337    pub auction_time: u64,
338    /// Continuous book clearing price.
339    pub cont_book_clr_price: i64,
340    /// Auction interest clearing price.
341    pub auct_interest_clr_price: i64,
342    // Short-selling restriction filling price.
343    pub ssr_filling_price: i64,
344    /// Indicative match price.
345    pub ind_match_price: i64,
346    pub upper_collar: i64,
347    pub lower_collar: i64,
348    pub paired_qty: u32,
349    pub total_imbalance_qty: u32,
350    pub market_imbalance_qty: u32,
351    pub auction_type: c_char,
352    pub side: c_char,
353    pub auction_status: u8,
354    pub freeze_status: u8,
355    pub num_extensions: u8,
356    pub unpaired_qty: u8,
357    pub unpaired_side: c_char,
358    pub significant_imbalance: c_char,
359    #[cfg_attr(feature = "serde", serde(skip))]
360    pub _dummy: [c_char; 4],
361}
362
363pub const GATEWAY_ERROR_MSG_TYPE_ID: u8 = 0x15;
364/// Gateway error message
365/// `hd.rtype = 0x15`
366#[repr(C)]
367#[derive(Clone, Debug, PartialEq, Eq)]
368#[cfg_attr(feature = "trivial_copy", derive(Copy))]
369#[cfg_attr(feature = "serde", derive(serde::Serialize))]
370pub struct GatewayErrorMsg {
371    pub hd: RecordHeader,
372    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
373    pub err: [c_char; 64],
374}
375
376pub const SYMBOL_MAPPING_MSG_TYPE_ID: u8 = 0x16;
377/// Symbol mapping message
378/// `hd.rtype = 0x16`
379#[repr(C)]
380#[derive(Clone, Debug, PartialEq, Eq)]
381#[cfg_attr(feature = "trivial_copy", derive(Copy))]
382#[cfg_attr(feature = "serde", derive(serde::Serialize))]
383pub struct SymbolMappingMsg {
384    pub hd: RecordHeader,
385    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
386    pub stype_in_symbol: [c_char; 22],
387    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_c_char_arr"))]
388    pub stype_out_symbol: [c_char; 22],
389    #[cfg_attr(feature = "serde", serde(skip))]
390    pub _dummy: [c_char; 4],
391    pub start_ts: u64,
392    pub end_ts: u64,
393}
394
395#[cfg(feature = "serde")]
396fn serialize_c_char_arr<S: serde::Serializer, const N: usize>(
397    arr: &[c_char; N],
398    serializer: S,
399) -> Result<S::Ok, S::Error> {
400    let cstr = unsafe { std::ffi::CStr::from_ptr(&arr[0]) };
401    let str = cstr.to_str().unwrap_or("<invalid UTF-8>");
402    serializer.serialize_str(str)
403}
404
405/// Serialize as a string to avoid any loss of precision with JSON serializers and parsers.
406#[cfg(feature = "serde")]
407fn serialize_large_u64<S: serde::Serializer>(num: &u64, serializer: S) -> Result<S::Ok, S::Error> {
408    serializer.serialize_str(&num.to_string())
409}
410
411/// Serialize an enum as its char representation.
412#[cfg(feature = "serde")]
413fn serialize_enum_as_char_repr<S: serde::Serializer, T: Copy + Into<u8>>(
414    val: &T,
415    serializer: S,
416) -> Result<S::Ok, S::Error> {
417    serializer.serialize_char(Into::<u8>::into(*val) as char)
418}
419
420/// A trait for objects with polymorphism based around [`RecordHeader::rtype`].
421pub trait ConstTypeId {
422    /// The value of [`RecordHeader::rtype`] for the implementing type.
423    const TYPE_ID: u8;
424}
425
426/// Provides a _relatively safe_ method for converting a reference to a
427/// struct beginning with the header into a [`RecordHeader`].
428/// Because it accepts a reference, the lifetime of the returned reference
429/// is tied to the input.
430///
431/// # Safety
432/// Although this function accepts a reference to a [`ConstTypeId`], it's assumed this struct's
433/// binary representation begins with a RecordHeader value
434pub unsafe fn transmute_into_header<T: ConstTypeId>(record: &T) -> &RecordHeader {
435    // Safety: because it comes from a reference, `header` must not be null. It's ok to cast to `mut`
436    // because it's never mutated.
437    let non_null = NonNull::from(record);
438    non_null.cast::<RecordHeader>().as_ref()
439}
440
441/// Provides a _relatively safe_ method for converting a reference to
442/// [`RecordHeader`] to a struct beginning with the header. Because it accepts a
443/// reference, the lifetime of the returned reference is tied to the input. This
444/// function checks `rtype` before casting to ensure `bytes` contains a `T`.
445///
446/// # Safety
447/// `raw` must contain at least `std::mem::size_of::<T>()` bytes and a valid
448/// [`RecordHeader`] instance.
449pub unsafe fn transmute_record_bytes<T: ConstTypeId>(bytes: &[u8]) -> Option<&T> {
450    assert!(
451        bytes.len() >= mem::size_of::<T>(),
452        concat!(
453            "Passing a slice smaller than `",
454            stringify!(T),
455            "` to `transmute_record_bytes` is invalid"
456        )
457    );
458    let non_null = NonNull::new_unchecked(bytes.as_ptr() as *mut u8);
459    if non_null.cast::<RecordHeader>().as_ref().rtype == T::TYPE_ID {
460        Some(non_null.cast::<T>().as_ref())
461    } else {
462        None
463    }
464}
465
466/// Provides a _relatively safe_ method for converting a view on bytes into a
467/// a [`RecordHeader`].
468/// Because it accepts a reference, the lifetime of the returned reference is
469/// tied to the input.
470///
471/// # Safety
472/// `bytes` must contain a complete record (not only the header). This is so that
473/// the header can be subsequently passed to transmute_record
474pub unsafe fn transmute_header_bytes(bytes: &[u8]) -> Option<&RecordHeader> {
475    assert!(
476        bytes.len() >= mem::size_of::<RecordHeader>(),
477        concat!(
478            "Passing a slice smaller than `",
479            stringify!(RecordHeader),
480            "` to `transmute_header_bytes` is invalid"
481        )
482    );
483    let non_null = NonNull::new_unchecked(bytes.as_ptr() as *mut u8);
484    let header = non_null.cast::<RecordHeader>().as_ref();
485    if header.length as usize * 4 > bytes.len() {
486        None
487    } else {
488        Some(header)
489    }
490}
491
492/// Provides a _relatively safe_ method for converting a reference to a
493/// [`RecordHeader`] to a struct beginning with the header. Because it accepts a reference,
494/// the lifetime of the returned reference is tied to the input.
495///
496/// # Safety
497/// Although this function accepts a reference to a [`RecordHeader`], it's assumed this is
498/// part of a larger `T` struct.
499pub unsafe fn transmute_record<T: ConstTypeId>(header: &RecordHeader) -> Option<&T> {
500    if header.rtype == T::TYPE_ID {
501        // Safety: because it comes from a reference, `header` must not be null. It's ok to cast to `mut`
502        // because it's never mutated.
503        let non_null = NonNull::from(header);
504        Some(non_null.cast::<T>().as_ref())
505    } else {
506        None
507    }
508}
509
510/// Provides a _relatively safe_ method for converting a mut reference to a
511/// [`RecordHeader`] to a struct beginning with the header. Because it accepts a reference,
512/// the lifetime of the returned reference is tied to the input.
513///
514/// # Safety
515/// Although this function accepts a reference to a [`RecordHeader`], it's assumed this is
516/// part of a larger `T` struct.
517pub unsafe fn transmute_record_mut<T: ConstTypeId>(header: &mut RecordHeader) -> Option<&mut T> {
518    if header.rtype == T::TYPE_ID {
519        // Safety: because it comes from a reference, `header` must not be null. It's ok to cast to `mut`
520        // because it's never mutated.
521        let non_null = NonNull::from(header);
522        Some(non_null.cast::<T>().as_mut())
523    } else {
524        None
525    }
526}
527
528impl ConstTypeId for MboMsg {
529    const TYPE_ID: u8 = TICK_MSG_TYPE_ID;
530}
531
532/// [TradeMsg]'s type ID is the size of its `booklevel` array (0) and is
533/// equivalent to MBP-0.
534impl ConstTypeId for TradeMsg {
535    const TYPE_ID: u8 = 0;
536}
537
538/// [Mbp1Msg]'s type ID is the size of its `booklevel` array.
539impl ConstTypeId for Mbp1Msg {
540    const TYPE_ID: u8 = 1;
541}
542
543/// [Mbp10Msg]'s type ID is the size of its `booklevel` array.
544impl ConstTypeId for Mbp10Msg {
545    const TYPE_ID: u8 = 10;
546}
547
548impl ConstTypeId for OhlcvMsg {
549    const TYPE_ID: u8 = OHLCV_TYPE_ID;
550}
551
552impl ConstTypeId for StatusMsg {
553    const TYPE_ID: u8 = STATUS_MSG_TYPE_ID;
554}
555
556impl ConstTypeId for InstrumentDefMsg {
557    const TYPE_ID: u8 = INSTRUMENT_DEF_MSG_TYPE_ID;
558}
559
560impl ConstTypeId for Imbalance {
561    const TYPE_ID: u8 = IMBALANCE_TYPE_ID;
562}
563
564impl ConstTypeId for GatewayErrorMsg {
565    const TYPE_ID: u8 = GATEWAY_ERROR_MSG_TYPE_ID;
566}
567
568impl ConstTypeId for SymbolMappingMsg {
569    const TYPE_ID: u8 = SYMBOL_MAPPING_MSG_TYPE_ID;
570}
571
572#[cfg(test)]
573mod tests {
574    use super::*;
575
576    const OHLCV_MSG: OhlcvMsg = OhlcvMsg {
577        hd: RecordHeader {
578            length: 56,
579            rtype: 17,
580            publisher_id: 1,
581            product_id: 5482,
582            ts_event: 1609160400000000000,
583        },
584        open: 372025000000000,
585        high: 372050000000000,
586        low: 372025000000000,
587        close: 372050000000000,
588        volume: 57,
589    };
590
591    #[test]
592    fn test_transmute_record_bytes() {
593        unsafe {
594            let ohlcv_bytes = std::slice::from_raw_parts(
595                &OHLCV_MSG as *const OhlcvMsg as *const u8,
596                mem::size_of::<OhlcvMsg>(),
597            )
598            .to_vec();
599            let ohlcv = transmute_record_bytes::<OhlcvMsg>(ohlcv_bytes.as_slice()).unwrap();
600            assert_eq!(*ohlcv, OHLCV_MSG);
601        };
602    }
603
604    #[test]
605    #[should_panic]
606    fn test_transmute_record_bytes_small_buffer() {
607        let source = OHLCV_MSG;
608        unsafe {
609            let slice = std::slice::from_raw_parts(
610                &source as *const OhlcvMsg as *const u8,
611                mem::size_of::<OhlcvMsg>() - 5,
612            );
613            transmute_record_bytes::<OhlcvMsg>(slice);
614        };
615    }
616
617    #[test]
618    fn test_transmute_record() {
619        let source = Box::new(OHLCV_MSG);
620        let ohlcv_ref: &OhlcvMsg = unsafe { transmute_record(&source.hd) }.unwrap();
621        assert_eq!(*ohlcv_ref, OHLCV_MSG);
622    }
623
624    #[test]
625    fn test_transmute_record_mut() {
626        let mut source = Box::new(OHLCV_MSG);
627        let ohlcv_ref: &OhlcvMsg = unsafe { transmute_record_mut(&mut source.hd) }.unwrap();
628        assert_eq!(*ohlcv_ref, OHLCV_MSG);
629    }
630
631    #[test]
632    fn test_symbol_mapping_size() {
633        assert_eq!(mem::size_of::<SymbolMappingMsg>(), 80);
634    }
635}