Skip to main content

digdigdig3_station/series/
data_point.rs

1//! `DataPoint` — common contract for everything a `Series<T>` can hold.
2//!
3//! Each market data class (trade, bar, ticker, OB snapshot, ...) provides:
4//! - a fixed on-disk record size in bytes,
5//! - encode/decode (little-endian, no allocations),
6//! - timestamp accessor (used by warm-start / range queries),
7//! - extractor from `digdigdig3::core::types::StreamEvent` (returns None if the
8//!   event is for a different stream class).
9//!
10//! Variable-length payload is supported via opt-in blob hooks: if a type's
11//! header carries a `(blob_offset, blob_len)` pointer pair at its tail, the
12//! companion `.blob` file holds the actual variable-length bytes. Types that
13//! do not need a blob inherit the default `None` impls and never trigger the
14//! `.blob` codepath.
15
16use digdigdig3::core::types::StreamEvent;
17
18/// Implemented by every data-class held in a [`crate::series::Series`].
19pub trait DataPoint: Sized + Clone + Send + Sync + 'static {
20    /// On-disk record size in bytes. MUST be constant for the type.
21    ///
22    /// For types that use blob storage, this includes the trailing 12-byte
23    /// `(blob_offset: u64, blob_len: u32)` pointer pair.
24    const RECORD_SIZE: usize;
25
26    /// Encode `self` to a fixed-size buffer (little-endian).
27    ///
28    /// For types using blob storage, the trailing 12-byte pointer is patched
29    /// by [`crate::series::DiskStore`] AFTER `encode` runs — implementors do
30    /// not need to fill it themselves. The buffer is zero-initialized.
31    fn encode(&self, out: &mut [u8]);
32
33    /// Decode from a fixed-size buffer. Returns None on malformed bytes.
34    ///
35    /// For types using blob storage, this path receives ONLY the header.
36    /// `DiskStore` calls [`Self::decode_blob`] instead when the blob slice
37    /// is needed to reconstruct string fields.
38    fn decode(bytes: &[u8]) -> Option<Self>;
39
40    /// Timestamp in milliseconds. Used for warm-start / range queries.
41    fn timestamp_ms(&self) -> i64;
42
43    /// Try to extract `Self` from a raw WS `StreamEvent`. Returns None if the
44    /// event doesn't carry data for this class.
45    fn from_stream_event(ev: &StreamEvent) -> Option<Self>;
46
47    /// Variable-length bytes to append to the companion `.blob` file.
48    ///
49    /// Default: `None` — type uses fixed-size storage only. Override on
50    /// types with string fields.
51    ///
52    /// Convention for multi-string variants: u16 length prefix per string,
53    /// then UTF-8 bytes; strings in fixed order matching the type definition.
54    fn encode_blob(&self) -> Option<Vec<u8>> { None }
55
56    /// Reconstruct `Self` from header bytes + blob slice.
57    ///
58    /// Default: ignore blob, call [`Self::decode`]. Override on types that
59    /// need to read string fields from the blob.
60    fn decode_blob(header: &[u8], _blob: &[u8]) -> Option<Self> {
61        Self::decode(header)
62    }
63
64    /// `Some(offset)` if the type uses blob storage; `None` otherwise.
65    ///
66    /// The pointer pair `(blob_offset: u64, blob_len: u32)` is written at
67    /// `&header[offset..offset+12]`. Convention: header tail, so
68    /// `offset = RECORD_SIZE - 12`. Returning `Some(_)` opts the type into
69    /// `.blob` file creation in [`crate::series::DiskStore`].
70    fn blob_pointer_offset() -> Option<usize> { None }
71}