Skip to main content

dbn/
record_ref.rs

1//! Non-owning dynamically-typed references to DBN records: [`RecordRef`] for immutable
2//! access and [`RecordRefMut`] for mutable access.
3
4use std::{fmt::Debug, hash, io::IoSlice, marker::PhantomData, mem, ptr::NonNull};
5
6use crate::{
7    record::{HasRType, Record, RecordHeader},
8    rtype_dispatch, RecordEnum, RecordMut, RecordRefEnum,
9};
10
11/// A wrapper around a non-owning immutable reference to a DBN record. This wrapper
12/// allows for mixing of record types and schemas and runtime record polymorphism.
13///
14/// Can hold any type implementing [`HasRType`] and acts similar to a `&dyn HasRType`
15/// or `&dyn Record` but due to the design of DBN records, only a few methods require a
16/// dynamic dispatch.
17///
18/// It has the [`has()`](Self::has) method for testing if the contained value is of a
19/// particular type, and the inner value can be downcasted to specific record types via
20/// the [`get()`](Self::get) method.
21///
22/// # Examples
23/// ```
24/// use dbn::{MboMsg, RecordRef, TradeMsg};
25///
26/// let mbo = MboMsg::default();
27/// let rec = RecordRef::from(&mbo);
28///
29/// // This isn't a trade
30/// assert!(!rec.has::<TradeMsg>());
31/// // It's an MBO record
32/// assert!(rec.has::<MboMsg>());
33///
34/// // `get()` can be used in `if let` chains:
35/// if let Some(_trade) = rec.get::<TradeMsg>() {
36///     panic!("Unexpected record type");
37/// } else if let Some(mbo) = rec.get::<MboMsg>() {
38///     println!("{mbo:?}");
39/// }
40/// ```
41///
42/// The common record header is directly accessible through the
43/// [`header()`](Self::header) method.
44#[derive(Copy, Clone)]
45pub struct RecordRef<'a> {
46    ptr: NonNull<RecordHeader>,
47    /// Associates the object with the lifetime of the memory pointed to by `ptr`.
48    _marker: PhantomData<&'a RecordHeader>,
49}
50
51/// The mutable counterpart to [`RecordRef`]. Wraps a mutable reference to a DBN
52/// record, allowing runtime polymorphism over record types while retaining the
53/// ability to modify the underlying data.
54///
55/// Use `RecordRefMut` when you need to mutate a record whose concrete type is
56/// determined at runtime. For immutable access, use [`RecordRef`]. For owned
57/// storage, use [`RecordBuf`](crate::RecordBuf).
58///
59/// # Examples
60/// ```
61/// use dbn::{MboMsg, RecordRefMut};
62///
63/// let mut mbo = MboMsg::default();
64/// let rec = RecordRefMut::from(&mut mbo);
65///
66/// // Downcast to the concrete type and mutate
67/// if let Some(inner) = rec.get_mut::<MboMsg>() {
68///     inner.price = 5_000_000_000; // $5.00
69///     inner.size = 10;
70/// }
71/// assert_eq!(mbo.price, 5_000_000_000);
72/// assert_eq!(mbo.size, 10);
73/// ```
74// Cannot be Copy or Clone
75pub struct RecordRefMut<'a> {
76    ptr: NonNull<RecordHeader>,
77    /// Associates the object with the lifetime of the memory pointed to by `ptr`.
78    _marker: PhantomData<&'a RecordHeader>,
79}
80
81// Safety: RecordRef exhibits immutable reference semantics similar to &T.
82// It should be safe to both send it across threads or access it simultaneously
83// (since the data is immutable).
84unsafe impl Send for RecordRef<'_> {}
85unsafe impl Sync for RecordRef<'_> {}
86
87// Safety: RecordRefMut exhibits mutable reference semantics similar to &mut T.
88// It should be safe to send it across threads (unique ownership of the referent).
89unsafe impl Send for RecordRefMut<'_> {}
90unsafe impl Sync for RecordRefMut<'_> {}
91
92impl<'a> RecordRef<'a> {
93    /// Constructs a new reference to the DBN record in `buffer`.
94    ///
95    /// # Safety
96    /// `buffer` should begin with a [`RecordHeader`] and contain a type implementing
97    /// [`HasRType`].
98    pub unsafe fn new(buffer: &'a [u8]) -> Self {
99        debug_assert!(
100            buffer.len() >= mem::size_of::<RecordHeader>(),
101            "buffer of length {} is too short",
102            buffer.len()
103        );
104
105        // Safety: casting to `*mut` to use `NonNull`, but `ptr` is still treated internally
106        // as an immutable reference
107        let raw_ptr = buffer.as_ptr() as *mut RecordHeader;
108
109        // Check if alignment of pointer matches that of header (and all records)
110        debug_assert_eq!(
111            raw_ptr.align_offset(std::mem::align_of::<RecordHeader>()),
112            0,
113            "unaligned buffer passed to `RecordRef::new`"
114        );
115        let ptr = NonNull::new_unchecked(raw_ptr.cast::<RecordHeader>());
116        Self {
117            ptr,
118            _marker: PhantomData,
119        }
120    }
121
122    /// Constructs a new reference to the DBN record.
123    ///
124    /// # Safety
125    /// `header` must point to a valid DBN record.
126    pub unsafe fn unchecked_from_header(header: *const RecordHeader) -> Self {
127        Self {
128            // `NonNull` requires `mut` but it is never mutated
129            ptr: NonNull::new_unchecked(header.cast_mut()),
130            _marker: PhantomData,
131        }
132    }
133
134    /// Returns `true` if the object points to a record of type `T`.
135    ///
136    /// Usually paired with [`get()`](Self::get) or [`get_unchecked()`](Self::get_unchecked).
137    ///
138    /// <div class="warning">
139    /// Only checks the <code>rtype</code> matches that of the type <code>T</code>. It does not check
140    /// the length.
141    /// </div>
142    ///
143    /// Use [`try_get()`](Self::try_get) when working with different versions of a DBN
144    /// struct.
145    ///
146    /// # Examples
147    /// ```
148    /// use dbn::{OhlcvMsg, RecordRef, Schema, TradeMsg};
149    ///
150    /// let bar = OhlcvMsg::default_for_schema(Schema::Ohlcv1M);
151    /// let rec = RecordRef::from(&bar);
152    ///
153    /// // This is a bar
154    /// assert!(rec.has::<OhlcvMsg>());
155    /// // It's not a trade
156    /// assert!(!rec.has::<TradeMsg>());
157    /// ```
158    pub fn has<T: HasRType>(&self) -> bool {
159        T::has_rtype(self.header().rtype)
160    }
161
162    /// Returns a reference to the underlying record of type `T` or `None` if it points
163    /// to another record type.
164    ///
165    /// Note: for safety, this method calls [`has::<T>()`](Self::has). To avoid a
166    /// duplicate check, use [`get_unchecked()`](Self::get_unchecked).
167    ///
168    /// # Panics
169    /// This function will panic if the rtype indicates it's of type `T` but the encoded
170    ///  length of the record is less than the size of `T`. Use [`try_get()`](Self::try_get)
171    /// to more gracefully handle versioned structs and the optional presence of [`crate::WithTsOut`].
172    ///
173    /// # Examples
174    /// ```
175    /// use dbn::{BboMsg, RecordRef, Schema};
176    ///
177    /// let bbo = BboMsg::default_for_schema(Schema::Bbo1S);
178    /// let rec = RecordRef::from(&bbo);
179    ///
180    /// if let Some(bbo) = rec.get::<BboMsg>() {
181    ///     println!("{bbo:?}");
182    /// }
183    /// ```
184    ///
185    /// With versioned DBN structs
186    /// ```should_panic
187    /// use dbn::{v1, v2, RecordRef};
188    ///
189    /// // Initialize with version 1 definition
190    /// let def = v1::InstrumentDefMsg::default();
191    /// let rec = RecordRef::from(&def);
192    /// // Try to extract a version 2 definition
193    /// let _def = rec.get::<v2::InstrumentDefMsg>();
194    /// ```
195    pub fn get<T: HasRType>(&self) -> Option<&'a T> {
196        if self.has::<T>() {
197            assert!(
198                self.record_size() >= mem::size_of::<T>(),
199                "Malformed `{}` record: expected length of at least {} bytes, found {} bytes. \
200                Confirm the DBN version in the Metadata header and the version upgrade policy",
201                std::any::type_name::<T>(),
202                mem::size_of::<T>(),
203                self.record_size()
204            );
205            // Safety: checked `rtype` in call to `has()`. Assumes the initial data based to
206            // `RecordRef` is indeed a record.
207            Some(unsafe { self.ptr.cast::<T>().as_ref() })
208        } else {
209            None
210        }
211    }
212
213    /// Like [`get()`](Self::get), but returns an error if the inner record is not a `T`
214    /// or has the correct `rtype` for `T`, but insufficient `length`. Never panics.
215    ///
216    /// # Errors
217    /// This function returns an error if does not hold a `T` or if its `rtype` matches
218    /// `T`, but its `length` is too short.
219    ///
220    /// # Examples
221    /// ```
222    /// use dbn::{v1, v2, v3, RecordRef, WithTsOut};
223    ///
224    /// // Initialize with version 1 definition
225    /// let def = v1::InstrumentDefMsg::default();
226    /// let rec = RecordRef::from(&def);
227    /// // Try to extract new versions of definitions
228    /// assert!(rec.try_get::<v2::InstrumentDefMsg>().is_err());
229    /// assert!(rec.try_get::<v3::InstrumentDefMsg>().is_err());
230    ///
231    /// rec.try_get::<v1::InstrumentDefMsg>().unwrap();
232    ///
233    /// // Also works with data that might have ts_out
234    /// assert!(rec.try_get::<WithTsOut<v1::InstrumentDefMsg>>().is_err());
235    /// ```
236    pub fn try_get<T: HasRType>(&self) -> crate::Result<&'a T> {
237        if self.has::<T>() {
238            if self.record_size() >= mem::size_of::<T>() {
239                // Safety: checked `rtype` in call to `has()` and size
240                Ok(unsafe { self.ptr.cast::<T>().as_ref() })
241            } else {
242                Err(crate::Error::conversion::<T>(format!(
243                    "{self:?} has insufficient length, may be an earlier version of this record"
244                )))
245            }
246        } else {
247            Err(crate::Error::conversion::<T>(format!(
248                "{self:?} has incorrect rtype"
249            )))
250        }
251    }
252
253    /// Returns a native Rust enum with a variant for each record type. This allows for
254    /// pattern `match`ing.
255    ///
256    /// # Errors
257    /// This function returns a conversion error if the rtype does not correspond with
258    /// any known DBN record type.
259    pub fn as_enum(&self) -> crate::Result<RecordRefEnum<'_>> {
260        RecordRefEnum::try_from(*self)
261    }
262
263    /// Returns a reference to the underlying record of type `T` without checking if
264    /// this object references a record of type `T`.
265    ///
266    /// For a safe alternative, see [`get()`](Self::get).
267    ///
268    /// # Safety
269    /// The caller needs to validate this object points to a `T`.
270    ///
271    /// # Examples
272    /// ```
273    /// use dbn::{BboMsg, RecordRef, Schema};
274    ///
275    /// let bbo = BboMsg::default_for_schema(Schema::Bbo1S);
276    /// let rec = RecordRef::from(&bbo);
277    ///
278    /// if rec.has::<BboMsg>() {
279    ///     // SAFETY: checked rtype
280    ///     println!("{:?}", unsafe { rec.get_unchecked::<BboMsg>() });
281    /// }
282    /// ```
283    pub unsafe fn get_unchecked<T: HasRType>(&self) -> &'a T {
284        debug_assert!(self.record_size() >= mem::size_of::<T>());
285        self.ptr.cast::<T>().as_ref()
286    }
287
288    /// Creates an owned [`RecordBuf`](crate::RecordBuf) by copying the record bytes.
289    ///
290    /// # Examples
291    /// ```
292    /// use dbn::{MboMsg, RecordRef};
293    ///
294    /// let mbo = MboMsg::default();
295    /// let rec_ref = RecordRef::from(&mbo);
296    /// let owned = rec_ref.to_owned();
297    /// assert!(owned == rec_ref);
298    /// ```
299    pub fn to_owned(&self) -> crate::RecordBuf {
300        // All valid records fit within MAX_RECORD_LEN.
301        crate::RecordBuf::try_from(*self).expect("record exceeds MAX_RECORD_LEN")
302    }
303}
304
305impl<'a, R> From<&'a R> for RecordRef<'a>
306where
307    R: HasRType,
308{
309    /// Constructs a new reference to a DBN record.
310    fn from(rec: &'a R) -> Self {
311        Self {
312            // Safety: `R` must be a record because it implements `HasRType`. Casting to `mut`
313            // is required for `NonNull`, but it is never mutated.
314            ptr: unsafe {
315                NonNull::new_unchecked((rec.header() as *const RecordHeader).cast_mut())
316            },
317            _marker: PhantomData,
318        }
319    }
320}
321
322impl<'a> AsRef<[u8]> for RecordRef<'a> {
323    fn as_ref(&self) -> &'a [u8] {
324        // # Safety
325        // Assumes the encoded record length is correct.
326        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.record_size()) }
327    }
328}
329
330impl<'a> Record for RecordRef<'a> {
331    fn header(&self) -> &'a RecordHeader {
332        // Safety: assumes `ptr` passes to a `RecordHeader`.
333        unsafe { self.ptr.as_ref() }
334    }
335
336    fn raw_index_ts(&self) -> u64 {
337        fn raw_index_ts<T: HasRType>(t: &T) -> u64 {
338            t.raw_index_ts()
339        }
340        rtype_dispatch!(self, raw_index_ts()).unwrap_or_else(|_| self.header().ts_event)
341    }
342}
343
344impl<'a> From<&'a RecordEnum> for RecordRef<'a> {
345    fn from(rec_enum: &'a RecordEnum) -> Self {
346        match rec_enum {
347            RecordEnum::Mbo(rec) => Self::from(rec),
348            RecordEnum::Trade(rec) => Self::from(rec),
349            RecordEnum::Mbp1(rec) => Self::from(rec),
350            RecordEnum::Mbp10(rec) => Self::from(rec),
351            RecordEnum::Ohlcv(rec) => Self::from(rec),
352            RecordEnum::Status(rec) => Self::from(rec),
353            RecordEnum::InstrumentDef(rec) => Self::from(rec),
354            RecordEnum::Imbalance(rec) => Self::from(rec),
355            RecordEnum::Stat(rec) => Self::from(rec),
356            RecordEnum::Error(rec) => Self::from(rec),
357            RecordEnum::SymbolMapping(rec) => Self::from(rec),
358            RecordEnum::System(rec) => Self::from(rec),
359            RecordEnum::Cmbp1(rec) => Self::from(rec),
360            RecordEnum::Bbo(rec) => Self::from(rec),
361            RecordEnum::Cbbo(rec) => Self::from(rec),
362        }
363    }
364}
365
366impl<'a> From<RecordRefEnum<'a>> for RecordRef<'a> {
367    fn from(rec_enum: RecordRefEnum<'a>) -> Self {
368        match rec_enum {
369            RecordRefEnum::Mbo(rec) => Self::from(rec),
370            RecordRefEnum::Trade(rec) => Self::from(rec),
371            RecordRefEnum::Mbp1(rec) => Self::from(rec),
372            RecordRefEnum::Mbp10(rec) => Self::from(rec),
373            RecordRefEnum::Ohlcv(rec) => Self::from(rec),
374            RecordRefEnum::Status(rec) => Self::from(rec),
375            RecordRefEnum::InstrumentDef(rec) => Self::from(rec),
376            RecordRefEnum::Imbalance(rec) => Self::from(rec),
377            RecordRefEnum::Stat(rec) => Self::from(rec),
378            RecordRefEnum::Error(rec) => Self::from(rec),
379            RecordRefEnum::SymbolMapping(rec) => Self::from(rec),
380            RecordRefEnum::System(rec) => Self::from(rec),
381            RecordRefEnum::Cmbp1(rec) => Self::from(rec),
382            RecordRefEnum::Bbo(rec) => Self::from(rec),
383            RecordRefEnum::Cbbo(rec) => Self::from(rec),
384        }
385    }
386}
387
388impl<'a> From<RecordRef<'a>> for IoSlice<'a> {
389    fn from(rec: RecordRef<'a>) -> Self {
390        // SAFETY: Assumes the encoded record length is correct.
391        Self::new(unsafe {
392            std::slice::from_raw_parts(rec.ptr.as_ptr() as *const u8, rec.record_size())
393        })
394    }
395}
396
397impl Debug for RecordRef<'_> {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        f.debug_struct("RecordRef")
400            .field(
401                "ptr",
402                &format_args!("{:?} --> {:?}", self.ptr, self.header()),
403            )
404            .finish()
405    }
406}
407
408impl<'a> RecordRefMut<'a> {
409    /// Constructs a new reference to the DBN record in `buffer`.
410    ///
411    /// # Safety
412    /// `buffer` should begin with a [`RecordHeader`] and contain a type implementing
413    /// [`HasRType`].
414    pub unsafe fn new(buffer: &'a mut [u8]) -> Self {
415        debug_assert!(buffer.len() >= mem::size_of::<RecordHeader>());
416
417        // Safety: casting to `*mut` to use `NonNull`, but `ptr` is still treated internally
418        // as an immutable reference
419        let raw_ptr = buffer.as_ptr() as *mut RecordHeader;
420
421        // Check if alignment of pointer matches that of header (and all records)
422        debug_assert_eq!(
423            raw_ptr.align_offset(std::mem::align_of::<RecordHeader>()),
424            0
425        );
426        let ptr = NonNull::new_unchecked(raw_ptr.cast::<RecordHeader>());
427        Self {
428            ptr,
429            _marker: PhantomData,
430        }
431    }
432
433    /// Constructs a new reference to the DBN record.
434    ///
435    /// # Safety
436    /// `header` must point to a valid, mutable DBN record.
437    pub unsafe fn unchecked_from_header(header: *mut RecordHeader) -> Self {
438        Self {
439            ptr: NonNull::new_unchecked(header),
440            _marker: PhantomData,
441        }
442    }
443
444    /// Returns `true` if the object points to a record of type `T`.
445    pub fn has<T: HasRType>(&self) -> bool {
446        T::has_rtype(self.header().rtype)
447    }
448
449    /// Returns a reference to the underlying record of type `T` or `None` if it points
450    /// to another record type.
451    ///
452    /// # Panics
453    /// This function will panic if the rtype indicates it's of type `T` but the encoded
454    /// length of the record is less than the size of `T`. Use [`try_get()`](Self::try_get)
455    /// to handle this gracefully.
456    pub fn get<T: HasRType>(&self) -> Option<&'a T> {
457        self.as_rec_ref().get()
458    }
459
460    /// Like [`get()`](Self::get), but returns an error instead of panicking when the
461    /// rtype matches but the length is insufficient.
462    ///
463    /// # Errors
464    /// This function returns an error if the buffer doesn't hold a `T`, or if the rtype
465    /// matches but the length is too short.
466    pub fn try_get<T: HasRType>(&self) -> crate::Result<&'a T> {
467        self.as_rec_ref().try_get()
468    }
469
470    /// Returns a mutable reference to the underlying record of type `T` or `None` if it
471    /// points to another record type.
472    ///
473    /// # Panics
474    /// This function will panic if the rtype indicates it's of type `T` but the encoded
475    /// length of the record is less than the size of `T`. Use
476    /// [`try_get_mut()`](Self::try_get_mut) to handle this gracefully.
477    ///
478    /// # Examples
479    /// ```
480    /// use dbn::{MboMsg, RecordRefMut, TradeMsg};
481    ///
482    /// let mut mbo = MboMsg::default();
483    /// let rec = RecordRefMut::from(&mut mbo);
484    ///
485    /// // Wrong type returns None
486    /// assert!(rec.get_mut::<TradeMsg>().is_none());
487    ///
488    /// // Correct type returns a mutable reference
489    /// rec.get_mut::<MboMsg>().unwrap().order_id = 42;
490    /// assert_eq!(mbo.order_id, 42);
491    /// ```
492    pub fn get_mut<T: HasRType>(&self) -> Option<&'a mut T> {
493        if self.has::<T>() {
494            assert!(
495                self.record_size() >= mem::size_of::<T>(),
496                "Malformed `{}` record: expected length of at least {} bytes, found {} bytes. \
497                Confirm the DBN version in the Metadata header and the version upgrade policy",
498                std::any::type_name::<T>(),
499                mem::size_of::<T>(),
500                self.record_size()
501            );
502            // SAFETY: checked rtype and size.
503            Some(unsafe { self.ptr.cast::<T>().as_mut() })
504        } else {
505            None
506        }
507    }
508
509    /// Like [`get_mut()`](Self::get_mut), but returns an error instead of panicking when
510    /// the rtype matches but the length is insufficient.
511    ///
512    /// # Errors
513    /// This function returns an error if the buffer doesn't hold a `T`, or if the rtype
514    /// matches but the length is too short.
515    ///
516    /// # Examples
517    /// ```
518    /// use dbn::{MboMsg, RecordRefMut};
519    ///
520    /// let mut mbo = MboMsg::default();
521    /// let mut rec = RecordRefMut::from(&mut mbo);
522    /// let inner = rec.try_get_mut::<MboMsg>().unwrap();
523    /// inner.price = 1_500_000_000;
524    /// assert_eq!(mbo.price, 1_500_000_000);
525    /// ```
526    pub fn try_get_mut<T: HasRType>(&mut self) -> crate::Result<&'a mut T> {
527        if self.has::<T>() {
528            if self.record_size() >= mem::size_of::<T>() {
529                // SAFETY: checked rtype and size.
530                Ok(unsafe { self.ptr.cast::<T>().as_mut() })
531            } else {
532                Err(crate::Error::conversion::<T>(format!(
533                    "{self:?} has insufficient length, may be an earlier version of this record"
534                )))
535            }
536        } else {
537            Err(crate::Error::conversion::<T>(format!(
538                "{self:?} has incorrect rtype"
539            )))
540        }
541    }
542
543    /// Returns an immutable reference to the underlying record of type `T` without
544    /// checking if this object references a record of type `T`.
545    ///
546    /// For a safe alternative, see [`get()`](Self::get).
547    ///
548    /// # Safety
549    /// The caller needs to validate this object points to a `T`.
550    pub unsafe fn get_unchecked<T: HasRType>(&self) -> &'a T {
551        debug_assert!(self.record_size() >= mem::size_of::<T>());
552        self.ptr.cast::<T>().as_ref()
553    }
554
555    /// Returns a mutable reference to the underlying record of type `T` without
556    /// checking if this object references a record of type `T`.
557    ///
558    /// For a safe alternative, see [`get_mut()`](Self::get_mut).
559    ///
560    /// # Safety
561    /// The caller needs to validate this object points to a `T`.
562    pub unsafe fn get_mut_unchecked<T: HasRType>(&mut self) -> &'a mut T {
563        debug_assert!(self.record_size() >= mem::size_of::<T>());
564        self.ptr.cast::<T>().as_mut()
565    }
566
567    /// Creates an owned [`RecordBuf`](crate::RecordBuf) by copying the record bytes.
568    ///
569    /// # Examples
570    /// ```
571    /// use dbn::{MboMsg, RecordRefMut};
572    ///
573    /// let mut mbo = MboMsg::default();
574    /// let rec = RecordRefMut::from(&mut mbo);
575    /// let owned = rec.to_owned();
576    /// assert!(owned.has::<MboMsg>());
577    /// ```
578    pub fn to_owned(&self) -> crate::RecordBuf {
579        // All valid records fit within MAX_RECORD_LEN.
580        crate::RecordBuf::try_from(self.as_rec_ref()).expect("record exceeds MAX_RECORD_LEN")
581    }
582
583    /// Returns an immutable [`RecordRef`] view of this mutable reference.
584    ///
585    /// # Examples
586    /// ```
587    /// use dbn::{MboMsg, RecordRef, RecordRefMut};
588    ///
589    /// let mut mbo = MboMsg::default();
590    /// let rec_mut = RecordRefMut::from(&mut mbo);
591    /// let rec_ref: RecordRef = rec_mut.as_rec_ref();
592    /// assert!(rec_ref.has::<MboMsg>());
593    /// ```
594    pub fn as_rec_ref(&self) -> RecordRef<'a> {
595        RecordRef {
596            ptr: self.ptr,
597            _marker: PhantomData,
598        }
599    }
600}
601
602impl<'a, R> From<&'a mut R> for RecordRefMut<'a>
603where
604    R: HasRType,
605{
606    /// Constructs a new reference to a DBN record.
607    fn from(rec: &'a mut R) -> Self {
608        Self {
609            // Safety: `R` must be a record because it implements `HasRType`. Casting to `mut`
610            // is required for `NonNull`, but it is never mutated.
611            ptr: unsafe {
612                NonNull::new_unchecked((rec.header() as *const RecordHeader).cast_mut())
613            },
614            _marker: PhantomData,
615        }
616    }
617}
618
619impl<'a> AsRef<[u8]> for RecordRefMut<'a> {
620    fn as_ref(&self) -> &'a [u8] {
621        // # Safety
622        // Assumes the encoded record length is correct.
623        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.record_size()) }
624    }
625}
626
627impl<'a> Record for RecordRefMut<'a> {
628    fn header(&self) -> &'a RecordHeader {
629        // Safety: assumes `ptr` points to a `RecordHeader`.
630        unsafe { self.ptr.as_ref() }
631    }
632
633    fn raw_index_ts(&self) -> u64 {
634        fn raw_index_ts<T: HasRType>(t: &T) -> u64 {
635            t.raw_index_ts()
636        }
637        rtype_dispatch!(self, raw_index_ts()).unwrap_or_else(|_| self.header().ts_event)
638    }
639}
640
641impl<'a> RecordMut for RecordRefMut<'a> {
642    fn header_mut(&mut self) -> &mut RecordHeader {
643        // Safety: assumes `ptr` points to a `RecordHeader`.
644        unsafe { self.ptr.as_mut() }
645    }
646}
647
648impl Debug for RecordRefMut<'_> {
649    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
650        f.debug_struct("RecordRefMut")
651            .field(
652                "ptr",
653                &format_args!("{:?} --> {:?}", self.ptr, self.header()),
654            )
655            .finish()
656    }
657}
658
659impl<'a, const CAP: usize> From<&'a crate::RecordBuf<CAP>> for RecordRef<'a> {
660    fn from(buf: &'a crate::RecordBuf<CAP>) -> Self {
661        buf.as_rec_ref()
662    }
663}
664
665impl<'a, const CAP: usize> From<&'a mut crate::RecordBuf<CAP>> for RecordRefMut<'a> {
666    fn from(buf: &'a mut crate::RecordBuf<CAP>) -> Self {
667        buf.as_rec_ref_mut()
668    }
669}
670
671impl<'a> From<RecordRefMut<'a>> for RecordRef<'a> {
672    fn from(ref_mut: RecordRefMut<'a>) -> Self {
673        ref_mut.as_rec_ref()
674    }
675}
676
677impl hash::Hash for RecordRef<'_> {
678    fn hash<H: hash::Hasher>(&self, state: &mut H) {
679        self.as_ref().hash(state);
680    }
681}
682
683impl hash::Hash for RecordRefMut<'_> {
684    fn hash<H: hash::Hasher>(&self, state: &mut H) {
685        self.as_ref().hash(state);
686    }
687}
688
689impl PartialEq for RecordRef<'_> {
690    fn eq(&self, other: &Self) -> bool {
691        *self.as_ref() == *other.as_ref()
692    }
693}
694
695impl Eq for RecordRef<'_> {}
696
697impl<const CAP: usize> PartialEq<crate::RecordBuf<CAP>> for RecordRef<'_> {
698    fn eq(&self, other: &crate::RecordBuf<CAP>) -> bool {
699        *self.as_ref() == *other.as_ref()
700    }
701}
702
703impl PartialEq<RecordRefMut<'_>> for RecordRef<'_> {
704    fn eq(&self, other: &RecordRefMut<'_>) -> bool {
705        *self.as_ref() == *other.as_ref()
706    }
707}
708
709impl PartialEq for RecordRefMut<'_> {
710    fn eq(&self, other: &Self) -> bool {
711        *self.as_ref() == *other.as_ref()
712    }
713}
714
715impl Eq for RecordRefMut<'_> {}
716
717impl<const CAP: usize> PartialEq<crate::RecordBuf<CAP>> for RecordRefMut<'_> {
718    fn eq(&self, other: &crate::RecordBuf<CAP>) -> bool {
719        *self.as_ref() == *other.as_ref()
720    }
721}
722
723impl PartialEq<RecordRef<'_>> for RecordRefMut<'_> {
724    fn eq(&self, other: &RecordRef<'_>) -> bool {
725        *self.as_ref() == *other.as_ref()
726    }
727}
728
729#[cfg(test)]
730mod tests {
731    use std::ffi::c_char;
732
733    use crate::{
734        enums::rtype, v1, v3, ErrorMsg, FlagSet, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg,
735        OhlcvMsg, TradeMsg,
736    };
737
738    use super::*;
739
740    const SOURCE_RECORD: MboMsg = MboMsg {
741        hd: RecordHeader::new::<MboMsg>(rtype::MBO, 1, 1, 0),
742        order_id: 17,
743        price: 0,
744        size: 32,
745        flags: FlagSet::empty(),
746        channel_id: 1,
747        action: 'A' as c_char,
748        side: 'B' as c_char,
749        ts_recv: 0,
750        ts_in_delta: 160,
751        sequence: 1067,
752    };
753
754    #[test]
755    fn test_header() {
756        let target = RecordRef::from(&SOURCE_RECORD);
757        assert_eq!(*target.header(), SOURCE_RECORD.hd);
758    }
759
760    #[test]
761    fn test_fmt_debug() {
762        let target = RecordRef::from(&SOURCE_RECORD);
763        let string = format!("{target:?}");
764        dbg!(&string);
765        assert!(string.starts_with("RecordRef { ptr: 0x"));
766        assert!(string.ends_with("--> RecordHeader { length: 14, rtype: Mbo, publisher_id: GlbxMdp3Glbx, instrument_id: 1, ts_event: 0 } }"));
767    }
768
769    #[test]
770    fn test_has_and_get() {
771        let target = RecordRef::from(&SOURCE_RECORD);
772        assert!(!target.has::<Mbp1Msg>());
773        assert!(!target.has::<Mbp10Msg>());
774        assert!(!target.has::<TradeMsg>());
775        assert!(!target.has::<ErrorMsg>());
776        assert!(!target.has::<OhlcvMsg>());
777        assert!(!target.has::<InstrumentDefMsg>());
778        assert!(target.has::<MboMsg>());
779        assert_eq!(*target.get::<MboMsg>().unwrap(), SOURCE_RECORD);
780    }
781
782    #[test]
783    fn test_as_ref() {
784        let target = RecordRef::from(&SOURCE_RECORD);
785        let byte_slice = target.as_ref();
786        assert_eq!(SOURCE_RECORD.record_size(), byte_slice.len());
787        assert_eq!(target.record_size(), byte_slice.len());
788    }
789
790    #[should_panic]
791    #[test]
792    fn test_get_too_short() {
793        let mut src = SOURCE_RECORD;
794        src.hd.length -= 1;
795        let target = RecordRef::from(&src);
796        // panic due to unexpected length
797        target.get::<MboMsg>();
798    }
799
800    #[should_panic]
801    #[test]
802    fn test_get_previous_ver() {
803        let src = v1::InstrumentDefMsg::default();
804        let target = RecordRef::from(&src);
805        // panic due to `src` having shorter record length despite matching rtypes
806        target.get::<v3::InstrumentDefMsg>();
807    }
808
809    #[test]
810    fn test_try_get_previous_ver() {
811        let src = v1::InstrumentDefMsg::default();
812        let target = RecordRef::from(&src);
813        assert!(
814            matches!(target.try_get::<v3::InstrumentDefMsg>(), Err(e) if e.to_string().contains("has insufficient length"))
815        );
816    }
817
818    #[test]
819    fn niche() {
820        assert_eq!(
821            std::mem::size_of::<RecordRef>(),
822            std::mem::size_of::<Option<RecordRef>>()
823        );
824        assert_eq!(
825            std::mem::size_of::<RecordRef>(),
826            std::mem::size_of::<usize>()
827        );
828    }
829
830    #[test]
831    fn test_record_ref_mut_get_delegates() {
832        let mut mbo = SOURCE_RECORD;
833        let target = RecordRefMut::from(&mut mbo);
834        assert!(target.has::<MboMsg>());
835        assert!(!target.has::<TradeMsg>());
836        assert_eq!(*target.get::<MboMsg>().unwrap(), SOURCE_RECORD);
837        assert!(target.get::<TradeMsg>().is_none());
838    }
839
840    #[test]
841    fn test_record_ref_mut_try_get() {
842        let mut def = v1::InstrumentDefMsg::default();
843        let target = RecordRefMut::from(&mut def);
844        target.try_get::<v1::InstrumentDefMsg>().unwrap();
845        assert!(
846            matches!(target.try_get::<v3::InstrumentDefMsg>(), Err(e) if e.to_string().contains("has insufficient length"))
847        );
848        assert!(
849            matches!(target.try_get::<MboMsg>(), Err(e) if e.to_string().contains("has incorrect rtype"))
850        );
851    }
852
853    #[test]
854    fn test_record_ref_mut_try_get_mut() {
855        let mut mbo = SOURCE_RECORD;
856        let mut target = RecordRefMut::from(&mut mbo);
857        let rec = target.try_get_mut::<MboMsg>().unwrap();
858        rec.price = 42;
859        assert_eq!(mbo.price, 42);
860    }
861
862    #[test]
863    fn test_record_ref_mut_get_mut() {
864        let mut mbo = SOURCE_RECORD;
865        let target = RecordRefMut::from(&mut mbo);
866        let rec = target.get_mut::<MboMsg>().unwrap();
867        rec.size = 99;
868        assert_eq!(mbo.size, 99);
869    }
870
871    #[test]
872    fn test_record_ref_mut_to_owned() {
873        let mut mbo = SOURCE_RECORD;
874        let target = RecordRefMut::from(&mut mbo);
875        let owned = target.to_owned();
876        assert_eq!(*owned.get::<MboMsg>().unwrap(), SOURCE_RECORD);
877    }
878
879    #[test]
880    fn test_record_ref_mut_as_rec_ref() {
881        let mut mbo = SOURCE_RECORD;
882        let target = RecordRefMut::from(&mut mbo);
883        let rec_ref: RecordRef = target.as_rec_ref();
884        assert_eq!(*rec_ref.get::<MboMsg>().unwrap(), SOURCE_RECORD);
885    }
886
887    #[test]
888    fn test_from_record_ref_mut_to_record_ref() {
889        let mut mbo = SOURCE_RECORD;
890        let target = RecordRefMut::from(&mut mbo);
891        let rec_ref: RecordRef = target.into();
892        assert_eq!(*rec_ref.get::<MboMsg>().unwrap(), SOURCE_RECORD);
893    }
894}