use structio::beve::header;
use structio::beve::reader::MAX_DEPTH;
use structio::{
Complex, ErrorCode, Matrix, MatrixLayout, MatrixRef, SkipUnknown, beve, beve_to_json,
from_beve, from_beve_with, from_str, to_beve, to_string, validate_beve,
};
fn size(n: u64) -> Vec<u8> {
assert!(n < 64, "one-byte sizes only");
vec![(n as u8) << 2]
}
fn object(members: &[(&str, Vec<u8>)]) -> Vec<u8> {
let mut out = vec![header::OBJECT];
out.extend(size(members.len() as u64));
for (key, value) in members {
out.extend(size(key.len() as u64));
out.extend_from_slice(key.as_bytes());
out.extend_from_slice(value);
}
out
}
fn wrap(n: usize, inner: &[u8]) -> Vec<u8> {
let mut out = inner.to_vec();
for _ in 0..n {
let mut next = vec![header::GENERIC_ARRAY];
next.extend(size(1));
next.extend_from_slice(&out);
out = next;
}
out
}
#[derive(Default, Debug)]
struct Any;
impl<'de> beve::Read<'de> for Any {
fn read<O: structio::Options>(
&mut self,
r: &mut beve::Reader<'de, O>,
) -> Result<(), ErrorCode> {
r.skip_value()
}
}
#[derive(Default, Debug, PartialEq)]
struct Two {
a: u32,
b: u32,
}
structio::object!(Two { a, b });
#[test]
fn a_lone_complex_is_the_extension_header_a_class_and_two_components() {
let mut want = vec![0x1E, 0x60];
want.extend_from_slice(&3.0f64.to_le_bytes());
want.extend_from_slice(&(-4.0f64).to_le_bytes());
assert_eq!(to_beve(&Complex::new(3.0f64, -4.0)), want);
}
#[test]
fn a_run_of_complex_numbers_is_one_header_and_one_block() {
let run = vec![Complex::new(1.0f64, 2.0), Complex::new(3.0, 4.0)];
let mut want = vec![0x1E, 0x61];
want.extend(size(2));
for v in [1.0f64, 2.0, 3.0, 4.0] {
want.extend_from_slice(&v.to_le_bytes());
}
assert_eq!(to_beve(&run), want);
assert_eq!(from_beve::<Vec<Complex<f64>>>(&want).unwrap(), run);
assert!(validate_beve(&want).is_ok());
let narrow = vec![Complex::new(-1.0f32, 0.5), Complex::new(2.0, -0.25)];
let mut want = vec![
header::COMPLEX,
header::complex_class(header::CAT_FLOAT, 2, header::COMPLEX_MANY),
];
want.extend(size(2));
for z in &narrow {
want.extend_from_slice(&z.re.to_le_bytes());
want.extend_from_slice(&z.im.to_le_bytes());
}
assert_eq!(to_beve(&narrow), want);
}
#[test]
fn every_component_type_round_trips_through_both_formats() {
macro_rules! check {
($($t:ty, $re:expr, $im:expr);* $(;)?) => {$({
let z: Complex<$t> = Complex::new($re, $im);
let run = vec![z, Complex::new($im, $re)];
assert_eq!(from_beve::<Complex<$t>>(&to_beve(&z)).unwrap(), z);
assert_eq!(from_beve::<Vec<Complex<$t>>>(&to_beve(&run)).unwrap(), run);
assert_eq!(from_str::<Complex<$t>>(&to_string(&z)).unwrap(), z);
assert_eq!(from_str::<Vec<Complex<$t>>>(&to_string(&run)).unwrap(), run);
assert_eq!(beve_to_json(&to_beve(&z)).unwrap(), to_string(&z));
assert_eq!(beve_to_json(&to_beve(&run)).unwrap(), to_string(&run));
assert!(validate_beve(&to_beve(&run)).is_ok());
})*}
}
check! {
f32, 1.5, -2.5;
f64, 1.5, -2.5;
i8, -1, 2; i16, -300, 4; i32, -70_000, 6; i64, -5_000_000_000, 8;
i128, i128::MIN, i128::MAX;
u8, 1, 2; u16, 300, 4; u32, 70_000, 6; u64, 5_000_000_000, 8;
u128, u128::MAX, 0;
}
}
#[test]
fn an_empty_run_is_still_a_run() {
let none: Vec<Complex<f64>> = Vec::new();
let bytes = to_beve(&none);
assert_eq!(bytes, [&[0x1E, 0x61][..], &size(0)].concat());
assert_eq!(from_beve::<Vec<Complex<f64>>>(&bytes).unwrap(), none);
assert_eq!(beve_to_json(&bytes).unwrap(), "[]");
}
#[test]
fn a_complex_array_is_not_confusable_with_a_numeric_one() {
let run = vec![Complex::new(1.0f64, 2.0), Complex::new(3.0, 4.0)];
let complex = to_beve(&run);
let plain = to_beve(&vec![1.0f64, 2.0, 3.0, 4.0]);
assert_eq!(complex[1], header::number(header::CAT_FLOAT, 3));
assert_eq!(
complex[1],
header::element_of(plain[0]),
"a complex array's class byte is the element header of the numeric \
array of the same width, which is the collision the synthetic one avoids"
);
assert_eq!(
from_beve::<Vec<f64>>(&complex).unwrap_err().code,
ErrorCode::ExpectedNumber
);
assert_eq!(
from_beve::<Vec<Complex<f64>>>(&plain).unwrap_err().code,
ErrorCode::ExpectedComplex
);
let bytes = to_beve(&vec![Complex::new(1i8, 2)]);
assert_eq!(
from_beve::<Vec<Complex<i8>>>(&bytes).unwrap(),
vec![Complex::new(1i8, 2)]
);
assert_eq!(
from_beve::<Vec<i8>>(&bytes).unwrap_err().code,
ErrorCode::ExpectedNumber
);
for width in 0..=4 {
for cat in [header::CAT_FLOAT, header::CAT_SIGNED, header::CAT_UNSIGNED] {
let elem =
header::complex_element(header::complex_class(cat, width, header::COMPLEX_MANY));
assert_eq!(header::ty(elem), header::TY_UNDEFINED, "{cat}/{width}");
assert!(
from_beve::<Any>(&[elem]).is_err(),
"{elem:#04x} is a value some document could hold"
);
}
}
}
#[test]
fn an_element_of_a_complex_array_is_a_pair_to_every_reader_of_it() {
let run = to_beve(&vec![Complex::new(1u8, 2), Complex::new(3, 4)]);
let elem = header::complex_element(run[1]);
assert_eq!(header::ty(elem), header::TY_UNDEFINED);
assert_eq!(from_beve::<Vec<Any>>(&run).map(|v| v.len()), Ok(2));
assert_eq!(
from_beve::<Vec<Matrix<u8>>>(&run).unwrap_err().code,
ErrorCode::ExpectedMatrix
);
assert_eq!(
from_beve::<Any>(&[elem]).unwrap_err().code,
ErrorCode::InvalidHeader
);
assert_eq!(
structio::from_beve_at::<Complex<u8>>(&run, "/1")
.unwrap_err()
.code,
ErrorCode::NoSuchValue
);
}
#[test]
fn a_stored_width_that_is_not_the_targets_widens_element_by_element() {
let narrow = to_beve(&vec![Complex::new(1.0f32, -2.0), Complex::new(3.0, -4.0)]);
assert_eq!(
from_beve::<Vec<Complex<f64>>>(&narrow).unwrap(),
vec![Complex::new(1.0f64, -2.0), Complex::new(3.0, -4.0)]
);
let small = to_beve(&Complex::new(7u8, 8));
assert_eq!(
from_beve::<Complex<i64>>(&small).unwrap(),
Complex::new(7i64, 8)
);
let big = to_beve(&Complex::new(0u64, u64::MAX));
assert_eq!(
from_beve::<Complex<u8>>(&big).unwrap_err().code,
ErrorCode::NumberOutOfRange
);
}
#[test]
fn a_two_element_array_reads_as_a_complex_number() {
let generic = {
let mut out = vec![header::GENERIC_ARRAY];
out.extend(size(2));
out.extend(to_beve(&1.5f64));
out.extend(to_beve(&-2.5f64));
out
};
assert_eq!(
from_beve::<Complex<f64>>(&generic).unwrap(),
Complex::new(1.5, -2.5)
);
assert_eq!(
from_beve::<Complex<f64>>(&to_beve(&vec![1.5f64, -2.5])).unwrap(),
Complex::new(1.5, -2.5)
);
assert_eq!(
from_str::<Complex<f64>>("[1.5,-2.5]").unwrap(),
Complex::new(1.5, -2.5)
);
}
#[test]
fn anything_that_is_not_a_pair_is_refused() {
for (name, bytes) in [
("a number", to_beve(&1.0f64)),
("a string", to_beve("1+2i")),
("an object", to_beve(&Two { a: 1, b: 2 })),
("a run of one", to_beve(&vec![Complex::new(1.0f64, 2.0)])),
] {
assert_eq!(
from_beve::<Complex<f64>>(&bytes).unwrap_err().code,
ErrorCode::ExpectedComplex,
"reading {name} as a complex number"
);
}
for wrong in [vec![1.0f64], vec![1.0, 2.0, 3.0]] {
assert_eq!(
from_beve::<Complex<f64>>(&to_beve(&wrong))
.unwrap_err()
.code,
ErrorCode::ExpectedComplex
);
assert_eq!(
from_str::<Complex<f64>>(&to_string(&wrong))
.unwrap_err()
.code,
ErrorCode::ExpectedComplex
);
}
}
#[test]
fn a_complex_member_that_is_not_wanted_is_stepped_over() {
for value in [
to_beve(&Complex::new(1.0f64, 2.0)),
to_beve(&vec![Complex::new(1u16, 2), Complex::new(3, 4)]),
] {
let doc = object(&[("a", to_beve(&1u32)), ("z", value), ("b", to_beve(&2u32))]);
assert_eq!(
from_beve_with::<SkipUnknown, Two>(&doc).unwrap(),
Two { a: 1, b: 2 }
);
assert!(validate_beve(&doc).is_ok());
}
}
#[test]
fn a_run_of_complex_numbers_costs_no_nesting_level() {
let inner = to_beve(&vec![Complex::new(1.0f32, 2.0), Complex::new(3.0, 4.0)]);
for wrappers in [
MAX_DEPTH as usize - 1,
MAX_DEPTH as usize,
MAX_DEPTH as usize + 1,
] {
let doc = wrap(wrappers, &inner);
let valid = validate_beve(&doc).is_ok();
assert_eq!(valid, wrappers <= MAX_DEPTH as usize, "{wrappers} wrappers");
assert_eq!(
valid,
read_nested::<Vec<Complex<f64>>>(&doc, wrappers).is_ok(),
"{wrappers} wrappers"
);
assert_eq!(valid, from_beve::<Any>(&doc).is_ok(), "{wrappers} wrappers");
}
let doc = wrap(MAX_DEPTH as usize, &to_beve(&vec![1.0f64, 2.0]));
assert_eq!(
validate_beve(&doc).unwrap_err().code,
ErrorCode::ExceededMaxDepth
);
}
fn read_nested<T>(bytes: &[u8], depth: usize) -> Result<T, ErrorCode>
where
T: for<'de> beve::Read<'de> + Default,
{
fn go<T>(r: &mut beve::Reader<'_>, left: usize, out: &mut T) -> Result<(), ErrorCode>
where
T: for<'de> beve::Read<'de>,
{
if left == 0 {
return beve::Read::read(out, r);
}
r.read_seq(|r, _| go(r, left - 1, out)).map(|_| ())
}
let mut out = T::default();
go(&mut beve::Reader::new(bytes), depth, &mut out)?;
Ok(out)
}
#[test]
fn a_matrix_costs_the_one_level_the_skipping_walk_charges_it() {
let inner = to_beve(&Matrix::new(MatrixLayout::RowMajor, vec![2], vec![1u8, 2]).unwrap());
for wrappers in [
MAX_DEPTH as usize - 3,
MAX_DEPTH as usize - 2,
MAX_DEPTH as usize - 1,
] {
let doc = wrap(wrappers, &inner);
let valid = validate_beve(&doc).is_ok();
assert_eq!(
valid,
wrappers + 2 <= MAX_DEPTH as usize,
"{wrappers} wrappers"
);
assert_eq!(
valid,
read_nested::<Matrix<i64>>(&doc, wrappers).is_ok(),
"{wrappers} wrappers"
);
}
}
#[test]
fn a_struct_of_complex_fields_gets_the_run_form_too() {
#[derive(Default, Debug, PartialEq)]
struct Pair {
a: Complex<f64>,
b: Complex<f64>,
}
structio::array!(Pair [Complex<f64>; a, b]);
let p = Pair {
a: Complex::new(1.0, 2.0),
b: Complex::new(3.0, 4.0),
};
assert_eq!(to_beve(&p), to_beve(&vec![p.a, p.b]));
assert_eq!(from_beve::<Pair>(&to_beve(&p)).unwrap(), p);
assert_eq!(to_string(&p), "[[1,2],[3,4]]");
}
#[test]
fn a_matrix_is_a_layout_byte_then_its_extents_then_its_data() {
let m = Matrix::new(MatrixLayout::RowMajor, vec![2, 3], (0..6u8).collect()).unwrap();
let mut want = vec![0x16, header::LAYOUT_RIGHT];
want.push(header::array_of(header::CAT_UNSIGNED, 0));
want.extend(size(2));
want.extend_from_slice(&[2, 3]);
want.extend(to_beve(&(0..6u8).collect::<Vec<_>>()));
assert_eq!(to_beve(&m), want);
assert_eq!(from_beve::<Matrix<u8>>(&want).unwrap(), m);
assert!(validate_beve(&want).is_ok());
}
#[test]
fn both_layouts_survive_both_formats() {
for layout in [MatrixLayout::RowMajor, MatrixLayout::ColumnMajor] {
let m = Matrix::new(layout, vec![3, 1], vec![1.5f64, 2.5, 3.5]).unwrap();
assert_eq!(from_beve::<Matrix<f64>>(&to_beve(&m)).unwrap(), m);
assert_eq!(from_str::<Matrix<f64>>(&to_string(&m)).unwrap(), m);
assert_eq!(beve_to_json(&to_beve(&m)).unwrap(), to_string(&m));
assert!(to_string(&m).contains(layout.as_str()));
for name in ["layout_right", "row_major", "right"] {
assert_eq!(name.parse(), Ok(MatrixLayout::RowMajor));
}
for name in ["layout_left", "column_major", "left"] {
assert_eq!(name.parse(), Ok(MatrixLayout::ColumnMajor));
}
assert_eq!(
"diagonal".parse::<MatrixLayout>(),
Err(ErrorCode::InvalidMatrixLayout)
);
}
}
#[test]
fn extents_are_stored_at_the_narrowest_width_that_holds_them() {
for (largest, code) in [(200usize, 0u8), (300, 1), (70_000, 2), (5_000_000_000, 3)] {
let m = Matrix::new(MatrixLayout::RowMajor, vec![0, largest], Vec::<u8>::new()).unwrap();
let bytes = to_beve(&m);
assert_eq!(
bytes[2],
header::array_of(header::CAT_UNSIGNED, code),
"extents up to {largest}"
);
assert_eq!(from_beve::<Matrix<u8>>(&bytes).unwrap(), m);
}
}
#[test]
fn a_matrix_reads_from_the_object_form_as_well() {
let m = Matrix::new(MatrixLayout::ColumnMajor, vec![2, 2], vec![1u32, 2, 3, 4]).unwrap();
let form = object(&[
("layout", to_beve("column_major")),
("extents", to_beve(&vec![2u8, 2])),
("value", to_beve(&vec![1u32, 2, 3, 4])),
]);
assert_eq!(from_beve::<Matrix<u32>>(&form).unwrap(), m);
let json = r#"{"layout":"layout_left","extents":[2,2],"value":[1,2,3,4]}"#;
assert_eq!(from_str::<Matrix<u32>>(json).unwrap(), m);
}
#[test]
fn a_matrix_member_the_shape_does_not_name_follows_the_policy() {
let m = Matrix::new(MatrixLayout::ColumnMajor, vec![2, 2], vec![1u32, 2, 3, 4]).unwrap();
let form = object(&[
("layout", to_beve("column_major")),
("extents", to_beve(&vec![2u8, 2])),
("value", to_beve(&vec![1u32, 2, 3, 4])),
("units", to_beve("volts")),
]);
assert_eq!(
from_beve::<Matrix<u32>>(&form).unwrap_err().code,
ErrorCode::UnknownKey
);
assert_eq!(
from_beve_with::<SkipUnknown, Matrix<u32>>(&form).unwrap(),
m
);
let json = r#"{"units":"volts","layout":"layout_left","extents":[2,2],"value":[1,2,3,4]}"#;
assert_eq!(
from_str::<Matrix<u32>>(json).unwrap_err().code,
ErrorCode::UnknownKey
);
assert_eq!(
structio::from_str_with::<SkipUnknown, Matrix<u32>>(json).unwrap(),
m
);
}
#[test]
fn a_matrix_of_complex_numbers_is_two_extensions_and_no_special_case() {
let values = vec![
Complex::new(1.0f64, 2.0),
Complex::new(3.0, 4.0),
Complex::new(5.0, 6.0),
Complex::new(7.0, 8.0),
];
let m = Matrix::new(MatrixLayout::RowMajor, vec![2, 2], values.clone()).unwrap();
let bytes = to_beve(&m);
assert!(bytes.ends_with(&to_beve(&values)));
assert_eq!(from_beve::<Matrix<Complex<f64>>>(&bytes).unwrap(), m);
assert!(validate_beve(&bytes).is_ok());
assert_eq!(beve_to_json(&bytes).unwrap(), to_string(&m));
assert_eq!(from_str::<Matrix<Complex<f64>>>(&to_string(&m)).unwrap(), m);
}
#[test]
fn a_shape_that_does_not_describe_its_data_cannot_be_built() {
assert_eq!(
Matrix::new(MatrixLayout::RowMajor, vec![2, 3], vec![1.0f64]).unwrap_err(),
ErrorCode::InvalidMatrixShape
);
assert!(Matrix::new(MatrixLayout::RowMajor, vec![0, 3], Vec::<f64>::new()).is_ok());
assert_eq!(Matrix::<f64>::default().len(), 0);
assert!(Matrix::<f64>::default().is_empty());
assert_eq!(Matrix::<f64>::default().rank(), 0);
assert_eq!(
Matrix::new(MatrixLayout::RowMajor, vec![usize::MAX, 2], vec![1.0f64]).unwrap_err(),
ErrorCode::InvalidMatrixShape
);
}
#[test]
fn a_matrix_that_fails_to_read_is_left_empty_rather_than_half_filled() {
let mut bad = vec![0x16, header::LAYOUT_LEFT];
bad.push(header::array_of(header::CAT_UNSIGNED, 0));
bad.extend(size(2));
bad.extend_from_slice(&[2, 3]);
bad.extend(to_beve(&vec![1.0f64, 2.0]));
assert!(validate_beve(&bad).is_ok());
assert_ne!(MatrixLayout::ColumnMajor, MatrixLayout::default());
let mut m = Matrix::new(MatrixLayout::RowMajor, vec![1], vec![9.0f64]).unwrap();
assert_eq!(
structio::read_beve_into(&mut m, &bad).unwrap_err().code,
ErrorCode::InvalidMatrixShape
);
assert_eq!(m.extents(), &[] as &[usize]);
assert_eq!(m.data(), &[] as &[f64]);
assert_eq!(m.layout(), MatrixLayout::default());
let mut m = Matrix::new(MatrixLayout::ColumnMajor, vec![1], vec![9.0f64]).unwrap();
assert!(structio::read_beve_into(&mut m, &bad[..bad.len() - 3]).is_err());
assert!(m.is_empty() && m.rank() == 0 && m.layout() == MatrixLayout::default());
}
#[test]
fn a_layout_byte_that_is_not_defined_is_refused() {
let mut bytes = to_beve(&Matrix::new(MatrixLayout::RowMajor, vec![2], vec![1u8, 2]).unwrap());
bytes[1] = 2;
assert_eq!(
from_beve::<Matrix<u8>>(&bytes).unwrap_err().code,
ErrorCode::InvalidMatrixLayout
);
assert!(validate_beve(&bytes).is_ok());
}
#[test]
fn anything_that_is_not_a_matrix_is_refused() {
for bytes in [to_beve(&1u8), to_beve("m"), to_beve(&vec![1u8, 2])] {
assert_eq!(
from_beve::<Matrix<u8>>(&bytes).unwrap_err().code,
ErrorCode::ExpectedMatrix
);
}
}
#[test]
fn a_matrix_member_that_is_not_wanted_is_stepped_over() {
let m = Matrix::new(
MatrixLayout::ColumnMajor,
vec![2, 2],
vec![1.0f64, 2.0, 3.0, 4.0],
)
.unwrap();
let doc = object(&[
("a", to_beve(&1u32)),
("z", to_beve(&m)),
("b", to_beve(&2u32)),
]);
assert_eq!(
from_beve_with::<SkipUnknown, Two>(&doc).unwrap(),
Two { a: 1, b: 2 }
);
assert_eq!(
structio::from_beve_at::<Matrix<f64>>(&doc, "/z").unwrap(),
m
);
}
#[test]
fn a_borrowed_matrix_writes_what_an_owned_one_would() {
let extents = [2usize, 2];
let data = [1.0f64, 2.0, 3.0, 4.0];
let view = MatrixRef::new(MatrixLayout::ColumnMajor, &extents, &data).unwrap();
let owned = view.to_matrix();
assert_eq!(to_beve(&view), to_beve(&owned));
assert_eq!(to_string(&view), to_string(&owned));
assert_eq!(from_beve::<Matrix<f64>>(&to_beve(&view)).unwrap(), owned);
assert_eq!(
MatrixRef::new(MatrixLayout::RowMajor, &extents, &data[..3]).unwrap_err(),
ErrorCode::InvalidMatrixShape
);
}
#[test]
fn the_pieces_come_back_apart() {
let mut m = Matrix::new(
MatrixLayout::RowMajor,
vec![2, 2],
vec![1.0f64, 2.0, 3.0, 4.0],
)
.unwrap();
m.data_mut()[0] = 9.0;
m.set_layout(MatrixLayout::ColumnMajor);
let (layout, extents, data) = m.into_parts();
assert_eq!(layout, MatrixLayout::ColumnMajor);
assert_eq!(extents, vec![2, 2]);
assert_eq!(data, vec![9.0, 2.0, 3.0, 4.0]);
}
#[test]
fn a_run_of_complex_numbers_streams_element_by_element() {
let run: Vec<Complex<f64>> = (0..64)
.map(|i| Complex::new(i as f64, -(i as f64)))
.collect();
let bytes = to_beve(&run);
let mut docs = beve::Documents::array(&bytes[..]).read_size(16);
let pulled: Vec<Complex<f64>> = docs.iter::<Complex<f64>>().map(Result::unwrap).collect();
assert_eq!(pulled, run);
let narrow = to_beve(&vec![Complex::new(1.0f32, 2.0), Complex::new(3.0, 4.0)]);
let mut docs = beve::Documents::array(&narrow[..]);
let pulled: Vec<Complex<f64>> = docs.iter::<Complex<f64>>().map(Result::unwrap).collect();
assert_eq!(pulled, vec![Complex::new(1.0, 2.0), Complex::new(3.0, 4.0)]);
let lone = to_beve(&Complex::new(1.0f64, 2.0));
let mut docs = beve::Documents::array(&lone[..]);
assert!(
docs.next_value::<Complex<f64>>()
.is_some_and(|r| r.is_err())
);
}