dbn/record/
methods.rs

1use std::fmt::Debug;
2
3use num_enum::TryFromPrimitive;
4
5use crate::{
6    enums::{ErrorCode, StatusAction, StatusReason, SystemCode},
7    pretty::px_to_f64,
8    SType, StatType, TradingEvent, TriState,
9};
10
11use super::*;
12
13impl RecordHeader {
14    /// The multiplier for converting the `length` field to the number of bytes.
15    pub const LENGTH_MULTIPLIER: usize = 4;
16
17    /// Creates a new `RecordHeader`. `R` and `rtype` should be compatible.
18    pub const fn new<R: HasRType>(
19        rtype: u8,
20        publisher_id: u16,
21        instrument_id: u32,
22        ts_event: u64,
23    ) -> Self {
24        Self {
25            length: (mem::size_of::<R>() / Self::LENGTH_MULTIPLIER) as u8,
26            rtype,
27            publisher_id,
28            instrument_id,
29            ts_event,
30        }
31    }
32
33    /// Returns the size of the **entire** record in bytes. The size of a `RecordHeader`
34    /// is constant.
35    pub const fn record_size(&self) -> usize {
36        self.length as usize * Self::LENGTH_MULTIPLIER
37    }
38
39    /// Tries to convert the raw record type into an enum.
40    ///
41    /// # Errors
42    /// This function returns an error if the `rtype` field does not
43    /// contain a valid, known [`RType`].
44    pub fn rtype(&self) -> crate::Result<RType> {
45        RType::try_from(self.rtype)
46            .map_err(|_| Error::conversion::<RType>(format!("{:#04X}", self.rtype)))
47    }
48
49    /// Tries to convert the raw `publisher_id` into an enum which is useful for
50    /// exhaustive pattern matching.
51    ///
52    /// # Errors
53    /// This function returns an error if the `publisher_id` does not correspond with
54    /// any known [`Publisher`].
55    pub fn publisher(&self) -> crate::Result<Publisher> {
56        Publisher::try_from(self.publisher_id)
57            .map_err(|_| Error::conversion::<Publisher>(self.publisher_id))
58    }
59
60    /// Parses the raw matching-engine-received timestamp into a datetime. Returns
61    /// `None` if `ts_event` contains the sentinel for a null timestamp.
62    pub fn ts_event(&self) -> Option<time::OffsetDateTime> {
63        if self.ts_event == crate::UNDEF_TIMESTAMP {
64            None
65        } else {
66            // u64::MAX is within maximum allowable range
67            Some(time::OffsetDateTime::from_unix_timestamp_nanos(self.ts_event as i128).unwrap())
68        }
69    }
70}
71
72impl Debug for RecordHeader {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        let mut debug_struct = f.debug_struct("RecordHeader");
75        debug_struct.field("length", &self.length);
76        match self.rtype() {
77            Ok(rtype) => debug_struct.field("rtype", &format_args!("{rtype:?}")),
78            Err(_) => debug_struct.field("rtype", &format_args!("{:#04X}", &self.rtype)),
79        };
80        match self.publisher() {
81            Ok(p) => debug_struct.field("publisher_id", &format_args!("{p:?}")),
82            Err(_) => debug_struct.field("publisher_id", &self.publisher_id),
83        };
84        debug_struct
85            .field("instrument_id", &self.instrument_id)
86            .field("ts_event", &self.ts_event)
87            .finish()
88    }
89}
90
91impl MboMsg {
92    /// Converts the order price to a floating point.
93    ///
94    /// `UNDEF_PRICE` will be converted to NaN.
95    ///
96    /// <div class="warning">
97    /// This may introduce floating-point error.
98    /// </div>
99    pub fn price_f64(&self) -> f64 {
100        px_to_f64(self.price)
101    }
102
103    /// Parses the action into an enum.
104    ///
105    /// # Errors
106    /// This function returns an error if the `action` field does not
107    /// contain a valid [`Action`].
108    pub fn action(&self) -> crate::Result<Action> {
109        Action::try_from(self.action as u8)
110            .map_err(|_| Error::conversion::<Action>(format!("{:#04X}", self.action as u8)))
111    }
112
113    /// Parses the side that initiates the event into an enum.
114    ///
115    /// # Errors
116    /// This function returns an error if the `side` field does not
117    /// contain a valid [`Side`].
118    pub fn side(&self) -> crate::Result<Side> {
119        Side::try_from(self.side as u8)
120            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
121    }
122
123    /// Parses the capture-server-received timestamp into a datetime.
124    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
125    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
126        ts_to_dt(self.ts_recv)
127    }
128
129    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
130    pub fn ts_in_delta(&self) -> time::Duration {
131        time::Duration::new(0, self.ts_in_delta)
132    }
133}
134
135impl BidAskPair {
136    /// Converts the bid price to a floating point.
137    ///
138    /// `UNDEF_PRICE` will be converted to NaN.
139    ///
140    /// <div class="warning">
141    /// This may introduce floating-point error.
142    /// </div>
143    pub fn bid_px_f64(&self) -> f64 {
144        px_to_f64(self.bid_px)
145    }
146
147    /// Converts the ask price to a floating point.
148    ///
149    /// `UNDEF_PRICE` will be converted to NaN.
150    ///
151    /// <div class="warning">
152    /// This may introduce floating-point error.
153    /// </div>
154    pub fn ask_px_f64(&self) -> f64 {
155        px_to_f64(self.ask_px)
156    }
157}
158
159impl ConsolidatedBidAskPair {
160    /// Converts the bid price to a floating point.
161    ///
162    /// `UNDEF_PRICE` will be converted to NaN.
163    ///
164    /// <div class="warning">
165    /// This may introduce floating-point error.
166    /// </div>
167    pub fn bid_px_f64(&self) -> f64 {
168        px_to_f64(self.bid_px)
169    }
170
171    /// Converts the ask price to a floating point.
172    ///
173    /// `UNDEF_PRICE` will be converted to NaN.
174    ///
175    /// <div class="warning">
176    /// This may introduce floating-point error.
177    /// </div>
178    pub fn ask_px_f64(&self) -> f64 {
179        px_to_f64(self.ask_px)
180    }
181
182    /// Parses the bid publisher into an enum.
183    ///
184    /// # Errors
185    /// This function returns an error if the `bid_pb` field does not
186    /// contain a valid [`Publisher`].
187    pub fn bid_pb(&self) -> crate::Result<Publisher> {
188        Publisher::try_from(self.bid_pb)
189            .map_err(|_| Error::conversion::<Publisher>(format!("{:#04X}", self.bid_pb)))
190    }
191
192    /// Parses the ask publisher into an enum.
193    ///
194    /// # Errors
195    /// This function returns an error if the `ask_pb` field does not
196    /// contain a valid [`Publisher`].
197    pub fn ask_pb(&self) -> crate::Result<Publisher> {
198        Publisher::try_from(self.ask_pb)
199            .map_err(|_| Error::conversion::<Publisher>(format!("{:#04X}", self.ask_pb)))
200    }
201}
202
203impl TradeMsg {
204    /// Converts the price to a floating point.
205    ///
206    /// `UNDEF_PRICE` will be converted to NaN.
207    ///
208    /// <div class="warning">
209    /// This may introduce floating-point error.
210    /// </div>
211    pub fn price_f64(&self) -> f64 {
212        px_to_f64(self.price)
213    }
214
215    /// Parses the action into an enum.
216    ///
217    /// # Errors
218    /// This function returns an error if the `action` field does not
219    /// contain a valid [`Action`].
220    pub fn action(&self) -> crate::Result<Action> {
221        Action::try_from(self.action as u8)
222            .map_err(|_| Error::conversion::<Action>(format!("{:#04X}", self.action as u8)))
223    }
224
225    /// Parses the side into an enum.
226    ///
227    /// # Errors
228    /// This function returns an error if the `side` field does not
229    /// contain a valid [`Side`].
230    pub fn side(&self) -> crate::Result<Side> {
231        Side::try_from(self.side as u8)
232            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
233    }
234
235    /// Parses the capture-server-received timestamp into a datetime.
236    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
237    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
238        ts_to_dt(self.ts_recv)
239    }
240
241    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
242    pub fn ts_in_delta(&self) -> time::Duration {
243        time::Duration::new(0, self.ts_in_delta)
244    }
245}
246
247impl Mbp1Msg {
248    /// Converts the order price to a floating point.
249    ///
250    /// `UNDEF_PRICE` will be converted to NaN.
251    ///
252    /// <div class="warning">
253    /// This may introduce floating-point error.
254    /// </div>
255    pub fn price_f64(&self) -> f64 {
256        px_to_f64(self.price)
257    }
258
259    /// Parses the action into an enum.
260    ///
261    /// # Errors
262    /// This function returns an error if the `action` field does not
263    /// contain a valid [`Action`].
264    pub fn action(&self) -> crate::Result<Action> {
265        Action::try_from(self.action as u8)
266            .map_err(|_| Error::conversion::<Action>(format!("{:#04X}", self.action as u8)))
267    }
268
269    /// Parses the side that initiates the event into an enum.
270    ///
271    /// # Errors
272    /// This function returns an error if the `side` field does not
273    /// contain a valid [`Side`].
274    pub fn side(&self) -> crate::Result<Side> {
275        Side::try_from(self.side as u8)
276            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
277    }
278
279    /// Parses the capture-server-received timestamp into a datetime.
280    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
281    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
282        ts_to_dt(self.ts_recv)
283    }
284
285    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
286    pub fn ts_in_delta(&self) -> time::Duration {
287        time::Duration::new(0, self.ts_in_delta)
288    }
289}
290
291impl Mbp10Msg {
292    /// Converts the order price to a floating point.
293    ///
294    /// `UNDEF_PRICE` will be converted to NaN.
295    ///
296    /// <div class="warning">
297    /// This may introduce floating-point error.
298    /// </div>
299    pub fn price_f64(&self) -> f64 {
300        px_to_f64(self.price)
301    }
302
303    /// Parses the action into an enum.
304    ///
305    /// # Errors
306    /// This function returns an error if the `action` field does not
307    /// contain a valid [`Action`].
308    pub fn action(&self) -> crate::Result<Action> {
309        Action::try_from(self.action as u8)
310            .map_err(|_| Error::conversion::<Action>(format!("{:#04X}", self.action as u8)))
311    }
312
313    /// Parses the side that initiates the event into an enum.
314    ///
315    /// # Errors
316    /// This function returns an error if the `side` field does not
317    /// contain a valid [`Side`].
318    pub fn side(&self) -> crate::Result<Side> {
319        Side::try_from(self.side as u8)
320            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
321    }
322
323    /// Parses the capture-server-received timestamp into a datetime.
324    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
325    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
326        ts_to_dt(self.ts_recv)
327    }
328
329    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
330    pub fn ts_in_delta(&self) -> time::Duration {
331        time::Duration::new(0, self.ts_in_delta)
332    }
333}
334
335impl BboMsg {
336    /// Converts the last trade price to a floating point.
337    ///
338    /// `UNDEF_PRICE` will be converted to NaN.
339    ///
340    /// <div class="warning">
341    /// This may introduce floating-point error.
342    /// </div>
343    pub fn price_f64(&self) -> f64 {
344        px_to_f64(self.price)
345    }
346
347    /// Parses the side that initiated the last trade into an enum.
348    ///
349    /// # Errors
350    /// This function returns an error if the `side` field does not
351    /// contain a valid [`Side`].
352    pub fn side(&self) -> crate::Result<Side> {
353        Side::try_from(self.side as u8)
354            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
355    }
356
357    /// Parses the end timestamp of the interval capture-server-received timestamp into a datetime.
358    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
359    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
360        ts_to_dt(self.ts_recv)
361    }
362}
363
364impl Cmbp1Msg {
365    /// Converts the order price to a floating point.
366    ///
367    /// `UNDEF_PRICE` will be converted to NaN.
368    ///
369    /// <div class="warning">
370    /// This may introduce floating-point error.
371    /// </div>
372    pub fn price_f64(&self) -> f64 {
373        px_to_f64(self.price)
374    }
375
376    /// Parses the action into an enum.
377    ///
378    /// # Errors
379    /// This function returns an error if the `action` field does not
380    /// contain a valid [`Action`].
381    pub fn action(&self) -> crate::Result<Action> {
382        Action::try_from(self.action as u8)
383            .map_err(|_| Error::conversion::<Action>(format!("{:#04X}", self.action as u8)))
384    }
385
386    /// Parses the side that initiates the event into an enum.
387    ///
388    /// # Errors
389    /// This function returns an error if the `side` field does not
390    /// contain a valid [`Side`].
391    pub fn side(&self) -> crate::Result<Side> {
392        Side::try_from(self.side as u8)
393            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
394    }
395
396    /// Parses the capture-server-received timestamp into a datetime.
397    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
398    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
399        ts_to_dt(self.ts_recv)
400    }
401
402    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
403    pub fn ts_in_delta(&self) -> time::Duration {
404        time::Duration::new(0, self.ts_in_delta)
405    }
406}
407
408impl CbboMsg {
409    /// Converts the last trade price to a floating point.
410    ///
411    /// `UNDEF_PRICE` will be converted to NaN.
412    ///
413    /// <div class="warning">
414    /// This may introduce floating-point error.
415    /// </div>
416    pub fn price_f64(&self) -> f64 {
417        px_to_f64(self.price)
418    }
419
420    /// Parses the side that initiated the last trade into an enum.
421    ///
422    /// # Errors
423    /// This function returns an error if the `side` field does not
424    /// contain a valid [`Side`].
425    pub fn side(&self) -> crate::Result<Side> {
426        Side::try_from(self.side as u8)
427            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
428    }
429
430    /// Parses the end timestamp of the interval capture-server-received timestamp into a datetime.
431    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
432    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
433        ts_to_dt(self.ts_recv)
434    }
435}
436
437impl OhlcvMsg {
438    /// Converts the open price to a floating point.
439    ///
440    /// `UNDEF_PRICE` will be converted to NaN.
441    ///
442    /// <div class="warning">
443    /// This may introduce floating-point error.
444    /// </div>
445    pub fn open_f64(&self) -> f64 {
446        px_to_f64(self.open)
447    }
448
449    /// Converts the high price to a floating point.
450    ///
451    /// `UNDEF_PRICE` will be converted to NaN.
452    ///
453    /// <div class="warning">
454    /// This may introduce floating-point error.
455    /// </div>
456    pub fn high_f64(&self) -> f64 {
457        px_to_f64(self.high)
458    }
459
460    /// Converts the low price to a floating point.
461    ///
462    /// `UNDEF_PRICE` will be converted to NaN.
463    ///
464    /// <div class="warning">
465    /// This may introduce floating-point error.
466    /// </div>
467    pub fn low_f64(&self) -> f64 {
468        px_to_f64(self.low)
469    }
470
471    /// Converts the close price to a floating point.
472    ///
473    /// `UNDEF_PRICE` will be converted to NaN.
474    ///
475    /// <div class="warning">
476    /// This may introduce floating-point error.
477    /// </div>
478    pub fn close_f64(&self) -> f64 {
479        px_to_f64(self.close)
480    }
481}
482
483impl StatusMsg {
484    /// Parses the capture-server-received timestamp into a datetime.
485    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
486    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
487        ts_to_dt(self.ts_recv)
488    }
489
490    /// Parses the action into an enum.
491    ///
492    /// # Errors
493    /// This function returns an error if the `action` field does not
494    /// contain a valid [`StatusAction`].
495    pub fn action(&self) -> crate::Result<StatusAction> {
496        StatusAction::try_from(self.action)
497            .map_err(|_| Error::conversion::<StatusAction>(format!("{:#04X}", self.action)))
498    }
499
500    /// Parses the reason into an enum.
501    ///
502    /// # Errors
503    /// This function returns an error if the `reason` field does not
504    /// contain a valid [`StatusReason`].
505    pub fn reason(&self) -> crate::Result<StatusReason> {
506        StatusReason::try_from(self.reason)
507            .map_err(|_| Error::conversion::<StatusReason>(format!("{:#04X}", self.reason)))
508    }
509
510    /// Parses the trading event into an enum.
511    ///
512    /// # Errors
513    /// This function returns an error if the `trading_event` field does not
514    /// contain a valid [`TradingEvent`].
515    pub fn trading_event(&self) -> crate::Result<TradingEvent> {
516        TradingEvent::try_from(self.trading_event)
517            .map_err(|_| Error::conversion::<TradingEvent>(format!("{:#04X}", self.trading_event)))
518    }
519
520    /// Parses the trading state into an `Option<bool>` where `None` indicates
521    /// a value is not applicable or available.
522    pub fn is_trading(&self) -> Option<bool> {
523        TriState::try_from_primitive(self.is_trading as c_char as u8)
524            .map(Option::<bool>::from)
525            .unwrap_or_default()
526    }
527
528    /// Parses the quoting state into an `Option<bool>` where `None` indicates
529    /// a value is not applicable or available.
530    pub fn is_quoting(&self) -> Option<bool> {
531        TriState::try_from_primitive(self.is_quoting as c_char as u8)
532            .map(Option::<bool>::from)
533            .unwrap_or_default()
534    }
535
536    /// Parses the short selling state into an `Option<bool>` where `None` indicates
537    /// a value is not applicable or available.
538    pub fn is_short_sell_restricted(&self) -> Option<bool> {
539        TriState::try_from_primitive(self.is_short_sell_restricted as c_char as u8)
540            .map(Option::<bool>::from)
541            .unwrap_or_default()
542    }
543}
544
545impl InstrumentDefMsg {
546    /// Parses the capture-server-received timestamp into a datetime.
547    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
548    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
549        ts_to_dt(self.ts_recv)
550    }
551
552    /// Converts the minimum constant tick to a floating point.
553    ///
554    /// `UNDEF_PRICE` will be converted to NaN.
555    ///
556    /// <div class="warning">
557    /// This may introduce floating-point error.
558    /// </div>
559    pub fn min_price_increment_f64(&self) -> f64 {
560        px_to_f64(self.min_price_increment)
561    }
562
563    /// Converts the display factor to a floating point.
564    ///
565    /// `UNDEF_PRICE` will be converted to NaN.
566    ///
567    /// <div class="warning">
568    /// This may introduce floating-point error.
569    /// </div>
570    pub fn display_factor_f64(&self) -> f64 {
571        px_to_f64(self.display_factor)
572    }
573
574    /// Parses the last eligible trade time into a datetime.
575    /// Returns `None` if `expiration` contains the sentinel for a null timestamp.
576    pub fn expiration(&self) -> Option<time::OffsetDateTime> {
577        ts_to_dt(self.expiration)
578    }
579
580    /// Parses the time of instrument activation into a datetime.
581    /// Returns `None` if `activation` contains the sentinel for a null timestamp.
582    pub fn activation(&self) -> Option<time::OffsetDateTime> {
583        ts_to_dt(self.activation)
584    }
585
586    /// Converts the high limit price to a floating point.
587    ///
588    /// `UNDEF_PRICE` will be converted to NaN.
589    ///
590    /// <div class="warning">
591    /// This may introduce floating-point error.
592    /// </div>
593    pub fn high_limit_price_f64(&self) -> f64 {
594        px_to_f64(self.high_limit_price)
595    }
596
597    /// Converts the low limit price to a floating point.
598    ///
599    /// `UNDEF_PRICE` will be converted to NaN.
600    ///
601    /// <div class="warning">
602    /// This may introduce floating-point error.
603    /// </div>
604    pub fn low_limit_price_f64(&self) -> f64 {
605        px_to_f64(self.low_limit_price)
606    }
607
608    /// Converts the differential value for price banding to a floating point.
609    ///
610    /// `UNDEF_PRICE` will be converted to NaN.
611    ///
612    /// <div class="warning">
613    /// This may introduce floating-point error.
614    /// </div>
615    pub fn max_price_variation_f64(&self) -> f64 {
616        px_to_f64(self.max_price_variation)
617    }
618
619    /// Converts the contract size for each instrument to a floating point.
620    ///
621    /// `UNDEF_PRICE` will be converted to NaN.
622    ///
623    /// <div class="warning">
624    /// This may introduce floating-point error.
625    /// </div>
626    pub fn unit_of_measure_qty_f64(&self) -> f64 {
627        px_to_f64(self.unit_of_measure_qty)
628    }
629
630    /// Converts the min price increment amount to a floating point.
631    ///
632    /// `UNDEF_PRICE` will be converted to NaN.
633    ///
634    /// <div class="warning">
635    /// This may introduce floating-point error.
636    /// </div>
637    pub fn min_price_increment_amount_f64(&self) -> f64 {
638        px_to_f64(self.min_price_increment_amount)
639    }
640
641    /// Converts the price ratio to a floating point.
642    ///
643    /// `UNDEF_PRICE` will be converted to NaN.
644    ///
645    /// <div class="warning">
646    /// This may introduce floating-point error.
647    /// </div>
648    pub fn price_ratio_f64(&self) -> f64 {
649        px_to_f64(self.price_ratio)
650    }
651
652    /// Converts the strike price to a floating point.
653    ///
654    /// `UNDEF_PRICE` will be converted to NaN.
655    ///
656    /// <div class="warning">
657    /// This may introduce floating-point error.
658    /// </div>
659    pub fn strike_price_f64(&self) -> f64 {
660        px_to_f64(self.strike_price)
661    }
662
663    /// Converts the leg price to a floating point.
664    ///
665    /// `UNDEF_PRICE` will be converted to NaN.
666    ///
667    /// <div class="warning">
668    /// This may introduce floating-point error.
669    /// </div>
670    pub fn leg_price_f64(&self) -> f64 {
671        px_to_f64(self.leg_price)
672    }
673
674    /// Converts the leg delta to a floating point.
675    ///
676    /// `UNDEF_PRICE` will be converted to NaN.
677    ///
678    /// <div class="warning">
679    /// This may introduce floating-point error.
680    /// </div>
681    pub fn leg_delta_f64(&self) -> f64 {
682        px_to_f64(self.leg_delta)
683    }
684
685    /// Parses the currency into a `&str`.
686    ///
687    /// # Errors
688    /// This function returns an error if `currency` contains invalid UTF-8.
689    pub fn currency(&self) -> crate::Result<&str> {
690        c_chars_to_str(&self.currency)
691    }
692
693    /// Parses the currency used for settlement into a `&str`.
694    ///
695    /// # Errors
696    /// This function returns an error if `settl_currency` contains invalid UTF-8.
697    pub fn settl_currency(&self) -> crate::Result<&str> {
698        c_chars_to_str(&self.settl_currency)
699    }
700
701    /// Parses the strategy type of the spread into a `&str`.
702    ///
703    /// # Errors
704    /// This function returns an error if `secsubtype` contains invalid UTF-8.
705    pub fn secsubtype(&self) -> crate::Result<&str> {
706        c_chars_to_str(&self.secsubtype)
707    }
708
709    /// Parses the raw symbol into a `&str`.
710    ///
711    /// # Errors
712    /// This function returns an error if `raw_symbol` contains invalid UTF-8.
713    pub fn raw_symbol(&self) -> crate::Result<&str> {
714        c_chars_to_str(&self.raw_symbol)
715    }
716
717    /// Parses the security group code into a `&str`.
718    ///
719    /// # Errors
720    /// This function returns an error if `group` contains invalid UTF-8.
721    pub fn group(&self) -> crate::Result<&str> {
722        c_chars_to_str(&self.group)
723    }
724
725    /// Parses the exchange into a `&str`.
726    ///
727    /// # Errors
728    /// This function returns an error if `exchange` contains invalid UTF-8.
729    pub fn exchange(&self) -> crate::Result<&str> {
730        c_chars_to_str(&self.exchange)
731    }
732
733    /// Parses the asset into a `&str`.
734    ///
735    /// # Errors
736    /// This function returns an error if `asset` contains invalid UTF-8.
737    pub fn asset(&self) -> crate::Result<&str> {
738        c_chars_to_str(&self.asset)
739    }
740
741    /// Parses the CFI code into a `&str`.
742    ///
743    /// # Errors
744    /// This function returns an error if `cfi` contains invalid UTF-8.
745    pub fn cfi(&self) -> crate::Result<&str> {
746        c_chars_to_str(&self.cfi)
747    }
748
749    /// Parses the security type into a `&str`.
750    ///
751    /// # Errors
752    /// This function returns an error if `security_type` contains invalid UTF-8.
753    pub fn security_type(&self) -> crate::Result<&str> {
754        c_chars_to_str(&self.security_type)
755    }
756
757    /// Parses the unit of measure into a `&str`.
758    ///
759    /// # Errors
760    /// This function returns an error if `unit_of_measure` contains invalid UTF-8.
761    pub fn unit_of_measure(&self) -> crate::Result<&str> {
762        c_chars_to_str(&self.unit_of_measure)
763    }
764
765    /// Parses the underlying into a `&str`.
766    ///
767    /// # Errors
768    /// This function returns an error if `underlying` contains invalid UTF-8.
769    pub fn underlying(&self) -> crate::Result<&str> {
770        c_chars_to_str(&self.underlying)
771    }
772
773    /// Parses the strike price currency into a `&str`.
774    ///
775    /// # Errors
776    /// This function returns an error if `strike_price_currency` contains invalid UTF-8.
777    pub fn strike_price_currency(&self) -> crate::Result<&str> {
778        c_chars_to_str(&self.strike_price_currency)
779    }
780
781    /// Parses the leg raw symbol into a `&str`.
782    ///
783    /// # Errors
784    /// This function returns an error if `leg_raw_symbol` contains invalid UTF-8.
785    pub fn leg_raw_symbol(&self) -> crate::Result<&str> {
786        c_chars_to_str(&self.leg_raw_symbol)
787    }
788
789    /// Parses the instrument class into an enum.
790    ///
791    /// # Errors
792    /// This function returns an error if the `instrument_class` field does not
793    /// contain a valid [`InstrumentClass`].
794    pub fn instrument_class(&self) -> crate::Result<InstrumentClass> {
795        InstrumentClass::try_from(self.instrument_class as u8).map_err(|_| {
796            Error::conversion::<InstrumentClass>(format!("{:#04X}", self.instrument_class as u8))
797        })
798    }
799
800    /// Parses the match algorithm into an enum.
801    ///
802    /// # Errors
803    /// This function returns an error if the `match_algorithm` field does not
804    /// contain a valid [`MatchAlgorithm`].
805    pub fn match_algorithm(&self) -> crate::Result<MatchAlgorithm> {
806        MatchAlgorithm::try_from(self.match_algorithm as u8).map_err(|_| {
807            Error::conversion::<MatchAlgorithm>(format!("{:#04X}", self.match_algorithm as u8))
808        })
809    }
810
811    /// Parses the security update action into an enum.
812    ///
813    /// # Errors
814    /// This function returns an error if the `security_update_action` field does not
815    /// contain a valid [`SecurityUpdateAction`].
816    pub fn security_update_action(&self) -> crate::Result<SecurityUpdateAction> {
817        SecurityUpdateAction::try_from(self.security_update_action as u8).map_err(|_| {
818            Error::conversion::<SecurityUpdateAction>(format!(
819                "{:#04X}",
820                self.security_update_action as u8
821            ))
822        })
823    }
824
825    /// Parses the user-defined instrument flag into an enum.
826    ///
827    /// # Errors
828    /// This function returns an error if the `user_defined_instrument` field does not
829    /// contain a valid [`UserDefinedInstrument`].
830    pub fn user_defined_instrument(&self) -> crate::Result<UserDefinedInstrument> {
831        UserDefinedInstrument::try_from(self.user_defined_instrument as u8).map_err(|_| {
832            Error::conversion::<UserDefinedInstrument>(format!(
833                "{:#04X}",
834                self.user_defined_instrument as u8
835            ))
836        })
837    }
838
839    /// Parses the leg instrument class into an enum.
840    ///
841    /// # Errors
842    /// This function returns an error if the `leg_instrument_class` field does not
843    /// contain a valid [`InstrumentClass`].
844    pub fn leg_instrument_class(&self) -> crate::Result<InstrumentClass> {
845        InstrumentClass::try_from(self.leg_instrument_class as u8).map_err(|_| {
846            Error::conversion::<InstrumentClass>(format!(
847                "{:#04X}",
848                self.leg_instrument_class as u8
849            ))
850        })
851    }
852
853    /// Parses the leg side into an enum.
854    ///
855    /// # Errors
856    /// This function returns an error if the `leg_side` field does not
857    /// contain a valid [`Side`].
858    pub fn leg_side(&self) -> crate::Result<Side> {
859        Side::try_from(self.leg_side as u8)
860            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.leg_side as u8)))
861    }
862}
863
864impl ImbalanceMsg {
865    /// Parses the capture-server-received timestamp into a datetime.
866    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
867    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
868        ts_to_dt(self.ts_recv)
869    }
870
871    /// Converts the ref price to a floating point.
872    ///
873    /// `UNDEF_PRICE` will be converted to NaN.
874    ///
875    /// <div class="warning">
876    /// This may introduce floating-point error.
877    /// </div>
878    pub fn ref_price_f64(&self) -> f64 {
879        px_to_f64(self.ref_price)
880    }
881
882    /// Parses the auction time into a datetime.
883    /// Returns `None` if `auction_time` contains the sentinel for a null timestamp.
884    pub fn auction_time(&self) -> Option<time::OffsetDateTime> {
885        ts_to_dt(self.auction_time)
886    }
887
888    /// Converts the cont book clr price to a floating point.
889    ///
890    /// `UNDEF_PRICE` will be converted to NaN.
891    ///
892    /// <div class="warning">
893    /// This may introduce floating-point error.
894    /// </div>
895    pub fn cont_book_clr_price_f64(&self) -> f64 {
896        px_to_f64(self.cont_book_clr_price)
897    }
898
899    /// Converts the auct interest clr price to a floating point.
900    ///
901    /// `UNDEF_PRICE` will be converted to NaN.
902    ///
903    /// <div class="warning">
904    /// This may introduce floating-point error.
905    /// </div>
906    pub fn auct_interest_clr_price_f64(&self) -> f64 {
907        px_to_f64(self.auct_interest_clr_price)
908    }
909
910    /// Converts the ssr filling price to a floating point.
911    ///
912    /// `UNDEF_PRICE` will be converted to NaN.
913    ///
914    /// <div class="warning">
915    /// This may introduce floating-point error.
916    /// </div>
917    pub fn ssr_filling_price_f64(&self) -> f64 {
918        px_to_f64(self.ssr_filling_price)
919    }
920
921    /// Converts the ind match price to a floating point.
922    ///
923    /// `UNDEF_PRICE` will be converted to NaN.
924    ///
925    /// <div class="warning">
926    /// This may introduce floating-point error.
927    /// </div>
928    pub fn ind_match_price_f64(&self) -> f64 {
929        px_to_f64(self.ind_match_price)
930    }
931
932    /// Converts the upper collar to a floating point.
933    ///
934    /// `UNDEF_PRICE` will be converted to NaN.
935    ///
936    /// <div class="warning">
937    /// This may introduce floating-point error.
938    /// </div>
939    pub fn upper_collar_f64(&self) -> f64 {
940        px_to_f64(self.upper_collar)
941    }
942
943    /// Converts the lower collar to a floating point.
944    ///
945    /// `UNDEF_PRICE` will be converted to NaN.
946    ///
947    /// <div class="warning">
948    /// This may introduce floating-point error.
949    /// </div>
950    pub fn lower_collar_f64(&self) -> f64 {
951        px_to_f64(self.lower_collar)
952    }
953
954    /// Parses the side into an enum.
955    ///
956    /// # Errors
957    /// This function returns an error if the `side` field does not
958    /// contain a valid [`Side`].
959    pub fn side(&self) -> crate::Result<Side> {
960        Side::try_from(self.side as u8)
961            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.side as u8)))
962    }
963
964    /// Parses the unpaired side into an enum.
965    ///
966    /// # Errors
967    /// This function returns an error if the `unpaired_side` field does not
968    /// contain a valid [`Side`].
969    pub fn unpaired_side(&self) -> crate::Result<Side> {
970        Side::try_from(self.unpaired_side as u8)
971            .map_err(|_| Error::conversion::<Side>(format!("{:#04X}", self.unpaired_side as u8)))
972    }
973}
974
975impl StatMsg {
976    /// Parses the capture-server-received timestamp into a datetime.
977    /// Returns `None` if `ts_recv` contains the sentinel for a null timestamp.
978    pub fn ts_recv(&self) -> Option<time::OffsetDateTime> {
979        ts_to_dt(self.ts_recv)
980    }
981
982    /// Parses the reference timestamp of the statistic value into a datetime.
983    /// Returns `None` if `ts_ref` contains the sentinel for a null timestamp.
984    pub fn ts_ref(&self) -> Option<time::OffsetDateTime> {
985        ts_to_dt(self.ts_ref)
986    }
987
988    /// Converts the value for price statistics to a floating point.
989    ///
990    /// `UNDEF_PRICE` will be converted to NaN.
991    ///
992    /// <div class="warning">
993    /// This may introduce floating-point error.
994    /// </div>
995    pub fn price_f64(&self) -> f64 {
996        px_to_f64(self.price)
997    }
998
999    /// Parses the difference between `ts_recv` and the matching-engine-sending timestamp into a duration.
1000    pub fn ts_in_delta(&self) -> time::Duration {
1001        time::Duration::new(0, self.ts_in_delta)
1002    }
1003
1004    /// Parses the type of statistic value into an enum.
1005    ///
1006    /// # Errors
1007    /// This function returns an error if the `stat_type` field does not
1008    /// contain a valid [`StatType`].
1009    pub fn stat_type(&self) -> crate::Result<StatType> {
1010        StatType::try_from(self.stat_type)
1011            .map_err(|_| Error::conversion::<StatType>(format!("{:#04X}", self.stat_type)))
1012    }
1013
1014    /// Parses the update action into an enum.
1015    ///
1016    /// # Errors
1017    /// This function returns an error if the `update_action` field does not
1018    /// contain a valid [`StatUpdateAction`].
1019    pub fn update_action(&self) -> crate::Result<StatUpdateAction> {
1020        StatUpdateAction::try_from(self.update_action).map_err(|_| {
1021            Error::conversion::<StatUpdateAction>(format!("{:#04X}", self.update_action))
1022        })
1023    }
1024}
1025
1026impl ErrorMsg {
1027    /// Creates a new `ErrorMsg`. `msg` will be truncated if it's too long.
1028    pub fn new(ts_event: u64, code: Option<ErrorCode>, msg: &str, is_last: bool) -> Self {
1029        let mut error = Self {
1030            hd: RecordHeader::new::<Self>(rtype::ERROR, 0, 0, ts_event),
1031            is_last: is_last as u8,
1032            ..Default::default()
1033        };
1034        if let Some(code) = code {
1035            error.code = code as u8;
1036        }
1037        // leave at least one null byte
1038        for (i, byte) in msg.as_bytes().iter().take(error.err.len() - 1).enumerate() {
1039            error.err[i] = *byte as c_char;
1040        }
1041        error
1042    }
1043
1044    /// Parses the error message into a `&str`.
1045    ///
1046    /// # Errors
1047    /// This function returns an error if `err` contains invalid UTF-8.
1048    pub fn err(&self) -> crate::Result<&str> {
1049        c_chars_to_str(&self.err)
1050    }
1051
1052    /// Parses the error code into an enum.
1053    ///
1054    /// # Errors
1055    /// This function returns an error if the `code` field does not
1056    /// contain a valid [`ErrorCode`].
1057    pub fn code(&self) -> crate::Result<ErrorCode> {
1058        ErrorCode::try_from(self.code)
1059            .map_err(|_| Error::conversion::<ErrorCode>(format!("{:#04X}", self.code)))
1060    }
1061}
1062
1063impl SymbolMappingMsg {
1064    /// Creates a new `SymbolMappingMsg`.
1065    ///
1066    /// # Errors
1067    /// This function returns an error if `stype_in_symbol` or `stype_out_symbol`
1068    /// contain more than maximum number of 70 characters.
1069    #[allow(clippy::too_many_arguments)]
1070    pub fn new(
1071        instrument_id: u32,
1072        ts_event: u64,
1073        stype_in: SType,
1074        stype_in_symbol: &str,
1075        stype_out: SType,
1076        stype_out_symbol: &str,
1077        start_ts: u64,
1078        end_ts: u64,
1079    ) -> crate::Result<Self> {
1080        Ok(Self {
1081            // symbol mappings aren't publisher-specific
1082            hd: RecordHeader::new::<Self>(rtype::SYMBOL_MAPPING, 0, instrument_id, ts_event),
1083            stype_in: stype_in as u8,
1084            stype_in_symbol: str_to_c_chars(stype_in_symbol)?,
1085            stype_out: stype_out as u8,
1086            stype_out_symbol: str_to_c_chars(stype_out_symbol)?,
1087            start_ts,
1088            end_ts,
1089        })
1090    }
1091
1092    /// Parses the stype in into an enum.
1093    ///
1094    /// # Errors
1095    /// This function returns an error if the `stype_in` field does not
1096    /// contain a valid [`SType`].
1097    pub fn stype_in(&self) -> crate::Result<SType> {
1098        SType::try_from(self.stype_in)
1099            .map_err(|_| Error::conversion::<SType>(format!("{:#04X}", self.stype_in)))
1100    }
1101
1102    /// Parses the input symbol into a `&str`.
1103    ///
1104    /// # Errors
1105    /// This function returns an error if `stype_in_symbol` contains invalid UTF-8.
1106    pub fn stype_in_symbol(&self) -> crate::Result<&str> {
1107        c_chars_to_str(&self.stype_in_symbol)
1108    }
1109
1110    /// Parses the stype out into an enum.
1111    ///
1112    /// # Errors
1113    /// This function returns an error if the `stype_out` field does not
1114    /// contain a valid [`SType`].
1115    pub fn stype_out(&self) -> crate::Result<SType> {
1116        SType::try_from(self.stype_out)
1117            .map_err(|_| Error::conversion::<SType>(format!("{:#04X}", self.stype_out)))
1118    }
1119
1120    /// Parses the output symbol into a `&str`.
1121    ///
1122    /// # Errors
1123    /// This function returns an error if `stype_out_symbol` contains invalid UTF-8.
1124    pub fn stype_out_symbol(&self) -> crate::Result<&str> {
1125        c_chars_to_str(&self.stype_out_symbol)
1126    }
1127
1128    /// Parses the start of the mapping interval into a datetime.
1129    /// Returns `None` if `start_ts` contains the sentinel for a null timestamp.
1130    pub fn start_ts(&self) -> Option<time::OffsetDateTime> {
1131        ts_to_dt(self.start_ts)
1132    }
1133
1134    /// Parses the end of the mapping interval into a datetime.
1135    /// Returns `None` if `end_ts` contains the sentinel for a null timestamp.
1136    pub fn end_ts(&self) -> Option<time::OffsetDateTime> {
1137        ts_to_dt(self.end_ts)
1138    }
1139}
1140
1141impl SystemMsg {
1142    pub(crate) const HEARTBEAT: &'static str = "Heartbeat";
1143
1144    /// Creates a new `SystemMsg`.
1145    ///
1146    /// # Errors
1147    /// This function returns an error if `msg` is too long.
1148    pub fn new(ts_event: u64, code: Option<SystemCode>, msg: &str) -> Result<Self> {
1149        Ok(Self {
1150            hd: RecordHeader::new::<Self>(rtype::SYSTEM, 0, 0, ts_event),
1151            msg: str_to_c_chars(msg)?,
1152            code: code.map(u8::from).unwrap_or(u8::MAX),
1153        })
1154    }
1155
1156    /// Creates a new heartbeat `SystemMsg`.
1157    pub fn heartbeat(ts_event: u64) -> Self {
1158        Self {
1159            hd: RecordHeader::new::<Self>(rtype::SYSTEM, 0, 0, ts_event),
1160            msg: str_to_c_chars(Self::HEARTBEAT).unwrap(),
1161            code: SystemCode::Heartbeat as u8,
1162        }
1163    }
1164
1165    /// Checks whether the message is a heartbeat from the gateway.
1166    pub fn is_heartbeat(&self) -> bool {
1167        if let Ok(code) = self.code() {
1168            code == SystemCode::Heartbeat
1169        } else {
1170            self.msg()
1171                .map(|msg| msg == Self::HEARTBEAT)
1172                .unwrap_or_default()
1173        }
1174    }
1175
1176    /// Parses the message from the Databento gateway into a `&str`.
1177    ///
1178    /// # Errors
1179    /// This function returns an error if `msg` contains invalid UTF-8.
1180    pub fn msg(&self) -> crate::Result<&str> {
1181        c_chars_to_str(&self.msg)
1182    }
1183
1184    /// Parses the type of system message into an enum.
1185    ///
1186    /// # Errors
1187    /// This function returns an error if the `code` field does not
1188    /// contain a valid [`SystemCode`].
1189    pub fn code(&self) -> crate::Result<SystemCode> {
1190        SystemCode::try_from(self.code)
1191            .map_err(|_| Error::conversion::<SystemCode>(format!("{:#04X}", self.code)))
1192    }
1193}