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}