use crate::api::rest::export::Dataset;
use crate::api::rest::greeks::GreekLevel;
use crate::domain::factors::FactorRow;
use crate::session::SimulationParametersV2;
use crate::utils::ChainError;
use chrono::{DateTime, Utc};
pub(super) const PACKED_MAGIC: &[u8; 4] = b"OCSP";
pub(super) const PACKED_VERSION: u32 = 1;
const ALIGNMENT: usize = 8;
pub(super) const PACKED_FOOTER_SENTINEL: u32 = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CellType {
F64,
I64,
Timestamp,
Dictionary,
LabelMask,
}
impl CellType {
#[must_use]
pub(super) fn code(self) -> u8 {
match self {
CellType::F64 => 0,
CellType::I64 => 1,
CellType::Timestamp => 2,
CellType::Dictionary => 3,
CellType::LabelMask => 4,
}
}
#[must_use]
pub(super) fn nullable(self) -> bool {
matches!(self, CellType::F64)
}
#[must_use]
pub(super) fn width(self) -> usize {
match self {
CellType::F64 | CellType::I64 | CellType::Timestamp | CellType::LabelMask => 8,
CellType::Dictionary => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum Cell {
F64(Option<f64>),
I64(i64),
Timestamp(i64),
Dictionary(u32),
LabelMask(u64),
}
impl Cell {
#[cfg(test)]
#[must_use]
pub(super) fn cell_type(&self) -> CellType {
match self {
Cell::F64(_) => CellType::F64,
Cell::I64(_) => CellType::I64,
Cell::Timestamp(_) => CellType::Timestamp,
Cell::Dictionary(_) => CellType::Dictionary,
Cell::LabelMask(_) => CellType::LabelMask,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct BinarySchema {
pub(super) names: Vec<&'static str>,
pub(super) types: Vec<CellType>,
pub(super) dictionary: Vec<String>,
}
impl BinarySchema {
#[must_use]
pub(super) fn new(
dataset: Dataset,
level: GreekLevel,
parameters: &SimulationParametersV2,
) -> Self {
let names = dataset.header(level);
let types = names
.iter()
.map(|name| match *name {
"step" => CellType::I64,
"simulated_at" | "expires_at" => CellType::Timestamp,
"symbol" => CellType::Dictionary,
"labels" => CellType::LabelMask,
_ => CellType::F64,
})
.collect();
let mut dictionary = vec![parameters.symbol.clone()];
let mut rule_ids: Vec<String> = parameters
.schedule
.rules()
.iter()
.map(|rule| rule.rule_id().to_string())
.collect();
rule_ids.sort();
rule_ids.dedup();
dictionary.extend(rule_ids);
Self {
names,
types,
dictionary,
}
}
#[must_use]
pub(super) fn symbol_index(&self) -> u32 {
0
}
pub(super) fn label_mask(&self, labels: &[String]) -> Result<u64, ChainError> {
let mut mask = 0_u64;
for label in labels {
let position = self
.dictionary
.iter()
.skip(1)
.position(|entry| entry == label)
.ok_or_else(|| {
ChainError::Internal(format!(
"the label {label:?} is not one of this simulation's rule ids"
))
})?;
if position >= u64::BITS as usize {
return Err(ChainError::Internal(format!(
"the label {label:?} is past the {MAX_LABEL_RULES} a mask carries"
)));
}
mask |= 1 << position;
}
Ok(mask)
}
#[cfg_attr(not(any(test, feature = "arrow-export")), allow(dead_code))]
#[must_use]
pub(super) fn labels_of(&self, mask: u64) -> Vec<&str> {
self.dictionary
.iter()
.skip(1)
.enumerate()
.filter(|(position, _)| *position < u64::BITS as usize && mask & (1 << position) != 0)
.map(|(_, entry)| entry.as_str())
.collect()
}
}
pub(super) const MAX_LABEL_RULES: usize = 63;
const _: () = assert!(
crate::domain::expiry::MAX_SCHEDULE_RULES <= MAX_LABEL_RULES,
"a schedule may carry more rules than a packed label mask has bits"
);
#[must_use]
pub(super) fn timestamp_nanos(instant: DateTime<Utc>) -> i64 {
instant.timestamp_nanos_opt().unwrap_or(i64::MAX)
}
pub(super) struct RowContext<'a> {
pub(super) schema: &'a BinarySchema,
pub(super) dataset: Dataset,
pub(super) level: GreekLevel,
pub(super) step: usize,
pub(super) simulated_at: DateTime<Utc>,
pub(super) row: &'a FactorRow,
pub(super) chains: Option<super::export::StepChains<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum RowFlow {
Continue,
Stop,
}
pub(super) fn visit_typed_rows<F>(
context: &RowContext<'_>,
visit: &mut F,
) -> Result<RowFlow, ChainError>
where
F: FnMut(Vec<Cell>) -> Result<RowFlow, ChainError>,
{
let RowContext {
schema,
dataset,
level,
step,
simulated_at,
row,
chains,
} = context;
let step_cell = Cell::I64(i64::try_from(*step).unwrap_or(i64::MAX));
let instant = Cell::Timestamp(timestamp_nanos(*simulated_at));
let symbol = Cell::Dictionary(schema.symbol_index());
match dataset {
Dataset::Underlying => visit(vec![
step_cell,
instant,
symbol,
Cell::F64(Some(row.spot.to_f64())),
]),
Dataset::Volatility => visit(vec![
step_cell,
instant,
symbol,
Cell::F64(Some(row.base_volatility.to_f64())),
]),
Dataset::OptionChains => {
let Some(chains) = chains else {
return Ok(RowFlow::Continue);
};
for expiration in chains.expirations() {
let expires_at = Cell::Timestamp(timestamp_nanos(expiration.expires_at));
let labels = Cell::LabelMask(schema.label_mask(expiration.labels)?);
for quote in expiration.quotes.quotes() {
let mut cells = vec![
step_cell,
instant,
symbol,
expires_at,
labels,
Cell::F64(Some(expiration.days_to_expiration)),
Cell::F64(Some(quote.strike)),
Cell::F64(Some(quote.implied_volatility)),
Cell::F64(quote.call_bid),
Cell::F64(quote.call_ask),
Cell::F64(quote.call_mid),
Cell::F64(quote.call_delta),
Cell::F64(quote.put_bid),
Cell::F64(quote.put_ask),
Cell::F64(quote.put_mid),
Cell::F64(quote.put_delta),
Cell::F64(quote.gamma),
];
if level.wants_greeks() {
for value in [
quote.call_greeks.theta,
quote.put_greeks.theta,
quote.call_greeks.vega,
quote.put_greeks.vega,
quote.call_greeks.rho,
quote.put_greeks.rho,
quote.call_greeks.rho_d,
quote.put_greeks.rho_d,
] {
cells.push(Cell::F64(value));
}
}
if matches!(level, GreekLevel::All) {
for value in [
quote.call_greeks.gamma,
quote.put_greeks.gamma,
quote.call_greeks.alpha,
quote.put_greeks.alpha,
quote.call_greeks.vanna,
quote.put_greeks.vanna,
quote.call_greeks.vomma,
quote.put_greeks.vomma,
quote.call_greeks.veta,
quote.put_greeks.veta,
quote.call_greeks.charm,
quote.put_greeks.charm,
quote.call_greeks.color,
quote.put_greeks.color,
] {
cells.push(Cell::F64(value));
}
}
if visit(cells)? == RowFlow::Stop {
return Ok(RowFlow::Stop);
}
}
}
Ok(RowFlow::Continue)
}
}
}
#[cfg(test)]
pub(super) fn typed_rows(context: &RowContext<'_>) -> Result<Vec<Vec<Cell>>, ChainError> {
let mut rows = Vec::new();
visit_typed_rows(context, &mut |cells| {
rows.push(cells);
Ok(RowFlow::Continue)
})?;
Ok(rows)
}
pub(super) struct PackedWriter {
schema: BinarySchema,
block_rows: usize,
buffered: Vec<Vec<Cell>>,
written: usize,
}
impl PackedWriter {
#[must_use]
pub(super) fn new(schema: BinarySchema, block_rows: usize) -> Self {
Self {
schema,
block_rows: block_rows.max(1),
buffered: Vec::new(),
written: 0,
}
}
#[must_use]
pub(super) fn schema(&self) -> &BinarySchema {
&self.schema
}
pub(super) fn header(&self) -> Result<Vec<u8>, ChainError> {
let mut out = Vec::new();
out.extend_from_slice(PACKED_MAGIC);
out.extend_from_slice(&PACKED_VERSION.to_le_bytes());
out.extend_from_slice(&u32_of(self.block_rows)?.to_le_bytes());
out.extend_from_slice(&u32_of(self.schema.dictionary.len())?.to_le_bytes());
for entry in &self.schema.dictionary {
out.extend_from_slice(&u32_of(entry.len())?.to_le_bytes());
out.extend_from_slice(entry.as_bytes());
}
out.extend_from_slice(&u32_of(self.schema.names.len())?.to_le_bytes());
for (name, cell_type) in self.schema.names.iter().zip(&self.schema.types) {
out.extend_from_slice(&u32_of(name.len())?.to_le_bytes());
out.extend_from_slice(name.as_bytes());
out.push(cell_type.code());
out.push(u8::from(cell_type.nullable()));
pad_to(&mut out, 4);
}
pad_to(&mut out, ALIGNMENT);
Ok(out)
}
pub(super) fn push_row(&mut self, row: Vec<Cell>) -> Result<Option<Vec<u8>>, ChainError> {
self.buffered.push(row);
if self.buffered.len() < self.block_rows {
return Ok(None);
}
let block = std::mem::take(&mut self.buffered);
self.written = self.written.saturating_add(block.len());
Ok(Some(self.encode_block(&block)?))
}
#[cfg(test)]
pub(super) fn push(&mut self, rows: Vec<Vec<Cell>>) -> Result<Vec<Vec<u8>>, ChainError> {
let mut blocks = Vec::new();
for row in rows {
if let Some(block) = self.push_row(row)? {
blocks.push(block);
}
}
Ok(blocks)
}
pub(super) fn flush(&mut self) -> Result<Option<Vec<u8>>, ChainError> {
let mut out = if self.buffered.is_empty() {
Vec::new()
} else {
let block = std::mem::take(&mut self.buffered);
self.written = self.written.saturating_add(block.len());
self.encode_block(&block)?
};
out.extend_from_slice(&PACKED_FOOTER_SENTINEL.to_le_bytes());
pad_to(&mut out, ALIGNMENT);
out.extend_from_slice(&(self.written as u64).to_le_bytes());
Ok(Some(out))
}
fn encode_block(&self, rows: &[Vec<Cell>]) -> Result<Vec<u8>, ChainError> {
let mut out = Vec::new();
out.extend_from_slice(&u32_of(rows.len())?.to_le_bytes());
pad_to(&mut out, ALIGNMENT);
for (index, cell_type) in self.schema.types.iter().enumerate() {
if cell_type.nullable() {
let mut bitmap = vec![0_u8; rows.len().div_ceil(8)];
for (position, row) in rows.iter().enumerate() {
let valid = matches!(row.get(index), Some(Cell::F64(Some(_))));
if let Some(byte) = bitmap.get_mut(position / 8)
&& valid
{
*byte |= 1 << (position % 8);
}
}
out.extend_from_slice(&bitmap);
pad_to(&mut out, ALIGNMENT);
}
for row in rows {
match row.get(index) {
Some(Cell::F64(value)) => {
out.extend_from_slice(&value.unwrap_or(0.0).to_le_bytes());
}
Some(Cell::I64(value) | Cell::Timestamp(value)) => {
out.extend_from_slice(&value.to_le_bytes());
}
Some(Cell::Dictionary(value)) => out.extend_from_slice(&value.to_le_bytes()),
Some(Cell::LabelMask(value)) => out.extend_from_slice(&value.to_le_bytes()),
None => out.extend_from_slice(&vec![0_u8; cell_type.width()]),
}
}
pad_to(&mut out, ALIGNMENT);
}
Ok(out)
}
}
fn pad_to(out: &mut Vec<u8>, boundary: usize) {
let remainder = out.len() % boundary;
if remainder != 0 {
out.extend(std::iter::repeat_n(0_u8, boundary - remainder));
}
}
fn u32_of(value: usize) -> Result<u32, ChainError> {
u32::try_from(value).map_err(|_| {
ChainError::Internal(format!("a packed length of {value} does not fit in a u32"))
})
}
pub(super) fn ensure_label_capacity(schema: &BinarySchema) -> Result<(), ChainError> {
let rules = match schema.dictionary.len() {
0 => 0,
length => length - 1,
};
if rules > MAX_LABEL_RULES {
return Err(ChainError::Internal(format!(
"a binary export carries at most {MAX_LABEL_RULES} schedule rules, \
this schema has {rules}"
)));
}
Ok(())
}
#[cfg(test)]
pub(super) fn decode_packed(bytes: &[u8]) -> Result<(Vec<String>, Vec<Vec<String>>), ChainError> {
use chrono::SecondsFormat;
let short = || ChainError::Internal("the packed document ended early".to_string());
let mut cursor = 0_usize;
let take = |cursor: &mut usize, count: usize| -> Result<&[u8], ChainError> {
let end = cursor.checked_add(count).ok_or_else(short)?;
let slice = bytes.get(*cursor..end).ok_or_else(short)?;
*cursor = end;
Ok(slice)
};
let take_u32 = |cursor: &mut usize| -> Result<u32, ChainError> {
let slice = take(cursor, 4)?;
let array: [u8; 4] = slice.try_into().map_err(|_| short())?;
Ok(u32::from_le_bytes(array))
};
let take_i64 = |cursor: &mut usize| -> Result<i64, ChainError> {
let slice = take(cursor, 8)?;
let array: [u8; 8] = slice.try_into().map_err(|_| short())?;
Ok(i64::from_le_bytes(array))
};
let align = |cursor: &mut usize| {
let remainder = *cursor % ALIGNMENT;
if remainder != 0 {
*cursor += ALIGNMENT - remainder;
}
};
if take(&mut cursor, 4)? != PACKED_MAGIC {
return Err(ChainError::Internal("not a packed document".to_string()));
}
let version = take_u32(&mut cursor)?;
if version != PACKED_VERSION {
return Err(ChainError::Internal(format!(
"unknown packed version {version}"
)));
}
let _block_rows = take_u32(&mut cursor)?;
let dictionary_len = take_u32(&mut cursor)? as usize;
let mut dictionary = Vec::with_capacity(dictionary_len);
for _ in 0..dictionary_len {
let len = take_u32(&mut cursor)? as usize;
let raw = take(&mut cursor, len)?;
dictionary
.push(String::from_utf8(raw.to_vec()).map_err(|_| {
ChainError::Internal("a dictionary entry is not UTF-8".to_string())
})?);
}
let column_count = take_u32(&mut cursor)? as usize;
let mut names = Vec::with_capacity(column_count);
let mut types = Vec::with_capacity(column_count);
for _ in 0..column_count {
let len = take_u32(&mut cursor)? as usize;
let raw = take(&mut cursor, len)?;
names.push(
String::from_utf8(raw.to_vec())
.map_err(|_| ChainError::Internal("a column name is not UTF-8".to_string()))?,
);
let code = *take(&mut cursor, 1)?.first().ok_or_else(short)?;
let _nullable = *take(&mut cursor, 1)?.first().ok_or_else(short)?;
types.push(match code {
0 => CellType::F64,
1 => CellType::I64,
2 => CellType::Timestamp,
3 => CellType::Dictionary,
4 => CellType::LabelMask,
other => {
return Err(ChainError::Internal(format!("unknown type code {other}")));
}
});
let remainder = cursor % 4;
if remainder != 0 {
cursor += 4 - remainder;
}
}
align(&mut cursor);
let schema = BinarySchema {
names: Vec::new(),
types: types.clone(),
dictionary: dictionary.clone(),
};
let mut rows: Vec<Vec<String>> = Vec::new();
let mut closed = false;
while cursor < bytes.len() {
let marker = take_u32(&mut cursor)?;
if marker == PACKED_FOOTER_SENTINEL {
align(&mut cursor);
let declared = take_i64(&mut cursor)? as usize;
if declared != rows.len() {
return Err(ChainError::Internal(format!(
"the footer declares {declared} rows, the blocks carried {}",
rows.len()
)));
}
closed = true;
break;
}
let row_count = marker as usize;
align(&mut cursor);
let mut block: Vec<Vec<String>> = vec![Vec::with_capacity(column_count); row_count];
for cell_type in &types {
let mut validity = vec![true; row_count];
if cell_type.nullable() {
let bitmap_len = row_count.div_ceil(8);
let bitmap = take(&mut cursor, bitmap_len)?.to_vec();
for (position, valid) in validity.iter_mut().enumerate() {
let byte = bitmap.get(position / 8).copied().unwrap_or(0);
*valid = byte & (1 << (position % 8)) != 0;
}
align(&mut cursor);
}
for (position, row) in block.iter_mut().enumerate() {
let rendered = match cell_type {
CellType::F64 => {
let slice = take(&mut cursor, 8)?;
let array: [u8; 8] = slice.try_into().map_err(|_| short())?;
if validity.get(position).copied().unwrap_or(false) {
f64::from_le_bytes(array).to_string()
} else {
String::new()
}
}
CellType::I64 => take_i64(&mut cursor)?.to_string(),
CellType::Timestamp => {
let nanos = take_i64(&mut cursor)?;
DateTime::from_timestamp_nanos(nanos)
.to_utc()
.to_rfc3339_opts(SecondsFormat::Secs, true)
}
CellType::Dictionary => {
let index = take_u32(&mut cursor)? as usize;
dictionary.get(index).cloned().unwrap_or_default()
}
CellType::LabelMask => {
let slice = take(&mut cursor, 8)?;
let array: [u8; 8] = slice.try_into().map_err(|_| short())?;
schema.labels_of(u64::from_le_bytes(array)).join("|")
}
};
row.push(rendered);
}
align(&mut cursor);
}
rows.extend(block);
}
if !closed {
return Err(ChainError::Internal(
"the packed document has no footer; the download was truncated".to_string(),
));
}
Ok((names, rows))
}
#[cfg(test)]
mod tests {
use super::*;
fn schema() -> BinarySchema {
BinarySchema {
names: vec!["step", "simulated_at", "symbol", "labels", "price"],
types: vec![
CellType::I64,
CellType::Timestamp,
CellType::Dictionary,
CellType::LabelMask,
CellType::F64,
],
dictionary: vec![
"SPX".to_string(),
"monthlies".to_string(),
"weeklies".to_string(),
"zero_dte".to_string(),
],
}
}
fn header_of(writer: &PackedWriter) -> Vec<u8> {
match writer.header() {
Ok(header) => header,
Err(error) => panic!("the header must encode: {error}"),
}
}
fn push_rows(writer: &mut PackedWriter, rows: Vec<Vec<Cell>>) -> Vec<Vec<u8>> {
match writer.push(rows) {
Ok(blocks) => blocks,
Err(error) => panic!("the rows must encode: {error}"),
}
}
fn flush_of(writer: &mut PackedWriter) -> Vec<u8> {
match writer.flush() {
Ok(Some(tail)) => tail,
Ok(None) => Vec::new(),
Err(error) => panic!("the flush must encode: {error}"),
}
}
fn row(step: i64, price: Option<f64>, mask: u64) -> Vec<Cell> {
vec![
Cell::I64(step),
Cell::Timestamp(1_767_623_400_000_000_000),
Cell::Dictionary(0),
Cell::LabelMask(mask),
Cell::F64(price),
]
}
#[test]
fn test_a_label_mask_round_trips() {
let schema = schema();
let labels = vec!["monthlies".to_string(), "zero_dte".to_string()];
let mask = match schema.label_mask(&labels) {
Ok(mask) => mask,
Err(error) => panic!("the labels are the schema's own rule ids: {error}"),
};
assert_eq!(schema.labels_of(mask), vec!["monthlies", "zero_dte"]);
assert_eq!(
schema.labels_of(mask).join("|"),
labels.join("|"),
"the reconstruction must be the csv value character for character"
);
}
#[test]
fn test_an_empty_label_set_is_a_zero_mask() {
let schema = schema();
match schema.label_mask(&[]) {
Ok(mask) => assert_eq!(mask, 0),
Err(error) => panic!("an empty label set must mask: {error}"),
}
assert!(schema.labels_of(0).is_empty());
}
#[test]
fn test_every_payload_offset_is_aligned() {
let schema = schema();
let mut writer = PackedWriter::new(schema.clone(), 4);
let header = header_of(&writer);
assert_eq!(header.len() % ALIGNMENT, 0, "the header must end aligned");
let blocks = push_rows(
&mut writer,
vec![
row(0, Some(1.0), 1),
row(1, None, 2),
row(2, Some(3.0), 4),
row(3, Some(4.0), 8),
],
);
assert_eq!(blocks.len(), 1, "four rows at a width of four is one block");
let block = &blocks[0];
let mut offset = 0_usize;
let rows = u32::from_le_bytes(match block[0..4].try_into() {
Ok(bytes) => bytes,
Err(error) => panic!("the row count must be readable: {error}"),
}) as usize;
assert_eq!(rows, 4);
offset += 4;
offset += (ALIGNMENT - offset % ALIGNMENT) % ALIGNMENT;
for cell_type in &schema.types {
assert_eq!(
offset % ALIGNMENT,
0,
"a payload must start aligned, got {offset}"
);
if cell_type.nullable() {
let bitmap = rows.div_ceil(8);
offset += bitmap + (ALIGNMENT - bitmap % ALIGNMENT) % ALIGNMENT;
assert_eq!(offset % ALIGNMENT, 0, "a bitmap must be padded");
}
let values = cell_type.width() * rows;
offset += values + (ALIGNMENT - values % ALIGNMENT) % ALIGNMENT;
}
assert_eq!(offset, block.len(), "the walk must consume the whole block");
}
#[test]
fn test_a_null_is_a_cleared_bit_rather_than_a_sentinel() {
let mut writer = PackedWriter::new(schema(), 2);
let mut document = header_of(&writer);
for block in push_rows(&mut writer, vec![row(0, Some(1.5), 0), row(1, None, 0)]) {
document.extend(block);
}
document.extend(flush_of(&mut writer));
match decode_packed(&document) {
Ok((_, rows)) => {
assert_eq!(rows[0][4], "1.5");
assert_eq!(rows[1][4], "", "a cleared validity bit reads as absent");
}
Err(error) => panic!("the document must decode: {error}"),
}
}
#[test]
fn test_rows_are_emitted_in_blocks_of_the_configured_width() {
let mut writer = PackedWriter::new(schema(), 2);
assert!(
push_rows(&mut writer, vec![row(0, Some(1.0), 0)]).is_empty(),
"a partial block waits"
);
assert_eq!(
push_rows(&mut writer, vec![row(1, Some(2.0), 0)]).len(),
1,
"the second row completes it"
);
assert!(
!flush_of(&mut writer).is_empty(),
"the footer closes the document even with nothing buffered"
);
}
#[test]
fn test_the_encoding_is_byte_identical_on_repeat() {
let rows = vec![row(0, Some(1.0), 3), row(1, None, 0)];
let mut first = PackedWriter::new(schema(), 2);
let mut second = PackedWriter::new(schema(), 2);
assert_eq!(header_of(&first), header_of(&second));
assert_eq!(
push_rows(&mut first, rows.clone()),
push_rows(&mut second, rows)
);
assert_eq!(flush_of(&mut first), flush_of(&mut second));
}
#[test]
fn test_the_writer_never_buffers_more_than_one_block() {
let block_rows = 8;
let mut writer = PackedWriter::new(schema(), block_rows);
for batch in 0..500 {
push_rows(
&mut writer,
(0..7)
.map(|index| row(batch * 7 + index, Some(1.0), 0))
.collect(),
);
assert!(
writer.buffered.len() < block_rows,
"a full block must be emitted rather than held: {} buffered",
writer.buffered.len()
);
}
let _ = flush_of(&mut writer);
assert!(
writer.buffered.is_empty(),
"the flush must empty the buffer"
);
}
#[test]
fn test_a_rule_id_equal_to_the_symbol_keeps_its_place() {
let schema = BinarySchema {
names: vec!["labels"],
types: vec![CellType::LabelMask],
dictionary: vec![
"weeklies".to_string(),
"monthlies".to_string(),
"weeklies".to_string(),
],
};
let labels = vec!["monthlies".to_string(), "weeklies".to_string()];
let mask = match schema.label_mask(&labels) {
Ok(mask) => mask,
Err(error) => panic!("both labels are rule ids: {error}"),
};
assert_eq!(
schema.labels_of(mask).join("|"),
"monthlies|weeklies",
"the order must be the text encodings' order"
);
}
#[test]
fn test_an_unknown_label_is_refused() {
match schema().label_mask(&["not_a_rule".to_string()]) {
Ok(mask) => panic!("an unknown label must not mask silently, got {mask}"),
Err(ChainError::Internal(message)) => {
assert!(message.contains("not_a_rule"), "it must name it: {message}");
}
Err(error) => panic!("expected an internal failure, got {error:?}"),
}
}
#[test]
fn test_a_truncated_document_is_refused() {
let mut writer = PackedWriter::new(schema(), 2);
let mut document = header_of(&writer);
for block in push_rows(
&mut writer,
vec![row(0, Some(1.0), 0), row(1, Some(2.0), 0)],
) {
document.extend(block);
}
document.extend(flush_of(&mut writer));
let truncated = &document[..document.len() - ALIGNMENT * 2];
match decode_packed(truncated) {
Ok((_, rows)) => panic!("a truncated document must not decode, got {rows:?}"),
Err(ChainError::Internal(message)) => {
assert!(message.contains("truncated"), "{message}");
}
Err(error) => panic!("expected an internal failure, got {error:?}"),
}
}
#[test]
fn test_too_many_rules_are_refused() {
let mut schema = schema();
schema.dictionary = std::iter::once("SPX".to_string())
.chain((0..MAX_LABEL_RULES + 1).map(|index| format!("rule_{index}")))
.collect();
match ensure_label_capacity(&schema) {
Ok(()) => panic!("a schema wider than the mask must be refused"),
Err(ChainError::Internal(message)) => {
assert!(
message.contains(&MAX_LABEL_RULES.to_string()),
"the failure must name the bound: {message}"
);
}
Err(error) => panic!("expected an internal failure, got {error:?}"),
}
}
#[test]
fn test_a_document_round_trips_through_the_decoder() {
let mut writer = PackedWriter::new(schema(), 2);
let mut bytes = header_of(&writer);
for block in push_rows(
&mut writer,
vec![
row(0, Some(1.5), 0b101),
row(1, None, 0),
row(2, Some(-3.25), 0b001),
],
) {
bytes.extend(block);
}
bytes.extend(flush_of(&mut writer));
match decode_packed(&bytes) {
Ok((names, rows)) => {
assert_eq!(
names,
vec!["step", "simulated_at", "symbol", "labels", "price"]
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][0], "0");
assert_eq!(rows[0][1], "2026-01-05T14:30:00Z");
assert_eq!(rows[0][2], "SPX");
assert_eq!(rows[0][3], "monthlies|zero_dte");
assert_eq!(rows[0][4], "1.5");
assert_eq!(rows[1][4], "", "a null decodes as an absent value");
assert_eq!(rows[2][4], "-3.25");
assert_eq!(rows[2][3], "monthlies");
}
Err(error) => panic!("the document must decode: {error}"),
}
}
#[test]
fn test_a_row_matches_its_schema() {
let schema = schema();
let row = row(0, Some(1.0), 0);
assert_eq!(row.len(), schema.types.len());
for (cell, expected) in row.iter().zip(&schema.types) {
assert_eq!(cell.cell_type(), *expected);
}
}
#[test]
fn test_a_schedule_that_fits_is_accepted() {
match ensure_label_capacity(&schema()) {
Ok(()) => {}
Err(error) => panic!("three rules must fit in a 64-bit mask: {error}"),
}
}
}