use std::collections::BTreeMap;
#[derive(Default, Debug, PartialEq)]
struct Run {
label: String,
samples: Vec<f64>,
valid: Vec<bool>,
channels: Vec<String>,
offsets: BTreeMap<u16, i32>,
}
structio::object!(Run {
label,
samples,
valid,
channels,
offsets
});
#[derive(Default, Debug, PartialEq)]
struct Frame<'a> {
id: u32,
payload: &'a [u8],
note: &'a str,
}
structio::beve_object!(['de] Frame<'de> { id, payload, note });
fn run() -> Run {
Run {
label: "sweep 3".into(),
samples: (0..1000).map(|i| i as f64 / 7.0).collect(),
valid: (0..1000).map(|i| i % 5 != 0).collect(),
channels: vec!["temp".into(), "pressure".into()],
offsets: BTreeMap::from([(1u16, -20), (2, 40)]),
}
}
fn send_frames(values: &[Run], sink: &mut impl std::io::Write) -> std::io::Result<()> {
let mut body = Vec::new();
for value in values {
structio::write_beve_into(value, &mut body);
sink.write_all(&(body.len() as u32).to_le_bytes())?;
sink.write_all(&body)?;
}
Ok(())
}
fn stream_frames(values: &[Run], sink: &mut impl std::io::Write) -> std::io::Result<()> {
for value in values {
sink.write_all(&(structio::beve_size(value) as u32).to_le_bytes())?;
structio::to_beve_writer(value, &mut *sink)?;
}
Ok(())
}
fn frame_aligned(query: &str, samples: &[f64]) -> Vec<u8> {
let mut frame = vec![0u8; 8]; frame.extend_from_slice(query.as_bytes());
let body = structio::beve_size_aligned_after(samples, frame.len());
frame[..8].copy_from_slice(&(body as u64).to_le_bytes());
structio::append_beve_aligned(samples, &mut frame);
frame
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let value = run();
let text = structio::to_string(&value);
let binary = structio::to_beve(&value);
assert_eq!(structio::from_str::<Run>(&text)?, value);
assert_eq!(structio::from_beve::<Run>(&binary)?, value);
println!("json {:>6} bytes", text.len());
println!(
"beve {:>6} bytes ({:.0}% of the text)",
binary.len(),
100.0 * binary.len() as f64 / text.len() as f64
);
println!(
" {} of those bytes are the sample payload, {} the flags",
value.samples.len() * 8,
value.valid.len().div_ceil(8)
);
let mut into = Run::default();
structio::read_beve_into(&mut into, &binary)?;
let at = into.samples.as_ptr();
structio::read_beve_into(&mut into, &binary)?;
assert_eq!(into.samples.as_ptr(), at, "the allocation was reused");
let frame = Frame {
id: 9,
payload: &[0xDE, 0xAD, 0xBE, 0xEF],
note: "no copies here",
};
let bytes = structio::to_beve(&frame);
let back: Frame = structio::from_beve(&bytes)?;
assert_eq!(back, frame);
let inside = (back.note.as_ptr() as usize) - (bytes.as_ptr() as usize) < bytes.len();
println!("borrowed fields point into the input: {inside}");
assert_eq!(
structio::from_beve::<Run>(&structio::to_beve_aligned(&value))?,
value
);
let padded = structio::to_beve_aligned(&value.samples);
let payload = padded.len() - value.samples.len() * 8;
assert_eq!(payload % 8, 0);
println!(
"the aligned form puts {} samples at byte {payload}",
value.samples.len()
);
let mut reader = structio::beve::Reader::new(&padded);
match reader.try_slice::<f64>() {
Some(block) => println!("borrowed {} samples with no copy at all", block.len()),
None => println!("this buffer is not one a &[f64] can point into"),
}
let mut file = Vec::new();
structio::to_beve_writer(&value, &mut file)?;
assert_eq!(file, binary);
let runs = [run(), run()];
let mut sink = Vec::new();
send_frames(&runs, &mut sink)?;
let mut streamed = Vec::new();
stream_frames(&runs, &mut streamed)?;
assert_eq!(streamed, sink);
let mut rest = &sink[..];
for want in &runs {
let (len, tail) = rest.split_at(4);
let len = u32::from_le_bytes(len.try_into()?) as usize;
let (frame, tail) = tail.split_at(len);
assert_eq!(&structio::from_beve::<Run>(frame)?, want);
rest = tail;
}
assert!(rest.is_empty());
println!("read back {} length-prefixed frames", runs.len());
for query in ["", "/sensor", "/a/rather/longer/route"] {
let frame = frame_aligned(query, &value.samples);
let base = 8 + query.len();
let stated = u64::from_le_bytes(frame[..8].try_into()?) as usize;
assert_eq!(frame.len() - base, stated);
assert_eq!((frame.len() - value.samples.len() * 8) % 8, 0);
assert_eq!(
structio::from_beve::<Vec<f64>>(&frame[base..])?,
value.samples
);
}
println!("aligned bodies stay aligned behind a header of any length");
assert_eq!(structio::beve_to_json(&binary)?, text);
let unknown = structio::beve_to_json(&structio::to_beve(&frame))?;
println!("a document with no declared type: {unknown}");
let archive = structio::to_beve(&vec![run(), run(), run()]);
let mut docs = structio::beve::Documents::array(&archive[..]).read_size(16);
let mut record = Run::default();
let mut count = 0;
while let Some(result) = docs.next_value_into(&mut record) {
result?;
count += 1;
}
println!(
"read {count} records out of {} bytes without ever holding the file",
archive.len()
);
let odd = vec![f64::NAN, f64::INFINITY, -0.0];
let back: Vec<f64> = structio::from_beve(&structio::to_beve(&odd))?;
assert!(back[0].is_nan() && back[1].is_infinite() && back[2].is_sign_negative());
println!("NaN, infinity, and negative zero survive the round trip");
let signal = vec![
structio::Complex::new(1.0f64, 2.0),
structio::Complex::new(3.0, -4.0),
];
let bytes = structio::to_beve(&signal);
println!(
"{} complex samples in {} bytes: one header, one count, and the components",
signal.len(),
bytes.len()
);
assert_eq!(
structio::from_beve::<Vec<structio::Complex<f64>>>(&bytes)?,
signal
);
let grid = structio::Matrix::new(structio::MatrixLayout::RowMajor, vec![1, 2], signal.clone())?;
let bytes = structio::to_beve(&grid);
assert_eq!(
structio::from_beve::<structio::Matrix<structio::Complex<f64>>>(&bytes)?,
grid
);
println!("as a matrix: {}", structio::beve_to_json(&bytes)?);
#[derive(Default, Debug, PartialEq)]
struct JustTheLabel {
label: String,
}
structio::object!(JustTheLabel { label });
let partial = structio::from_beve_with::<structio::SkipUnknown, JustTheLabel>(&binary)?;
assert_eq!(partial.label, value.label);
println!("unknown members are stepped over: {:?}", partial.label);
Ok(())
}