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}