verit 0.2.0

Exavian Veritate — zero-copy, self-describing, schema-evolvable binary serialization, safe on untrusted bytes, no unsafe, byte-identical across independent implementations.
Documentation
//! Dense structs: no presence bitmap, all fields mandatory. Smaller wire
//! size, loud error if a field is missing, still evolvable by ID.

use verit::{encode, Dt, Error, Message, Ref, Resolver, SchemaBuilder, SchemaMode, Value};

fn dense_point() -> verit::Schema {
    SchemaBuilder::new()
        .add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct("Shape", vec![(1, "pts", Dt::list(Dt::named("Point")))])
        .build("Shape")
        .unwrap()
}

#[test]
fn dense_struct_roundtrips() {
    let schema = dense_point();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(
            1,
            Value::List(vec![
                Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(2.0))]),
                Value::Struct(vec![(1, Value::F64(3.0)), (2, Value::F64(4.0))]),
            ]),
        )]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    let resolver = Resolver::identity(&schema).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    let pts = root.get_list(1).unwrap().unwrap();
    match pts.get(1).unwrap() {
        Ref::Struct(p) => {
            assert_eq!(p.get_f64(1).unwrap(), Some(3.0));
            assert_eq!(p.get_f64(2).unwrap(), Some(4.0));
        }
        other => panic!("expected struct, got {}", other.kind()),
    }
}

#[test]
fn dense_struct_is_16_not_24_bytes_per_element() {
    let dense = dense_point();
    let sparse = SchemaBuilder::new()
        .add_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct("Shape", vec![(1, "pts", Dt::list(Dt::named("Point")))])
        .build("Shape")
        .unwrap();
    let value = |n: usize| {
        Value::Struct(vec![(
            1,
            Value::List(
                (0..n)
                    .map(|i| {
                        Value::Struct(vec![
                            (1, Value::F64(i as f64)),
                            (2, Value::F64(-(i as f64))),
                        ])
                    })
                    .collect(),
            ),
        )])
    };
    let d = encode(&dense, &value(10), SchemaMode::HashOnly).unwrap();
    let s = encode(&sparse, &value(10), SchemaMode::HashOnly).unwrap();
    // 10 elements * (24 - 16) bytes of bitmap+padding saved.
    assert_eq!(s.len() - d.len(), 80);
}

#[test]
fn dense_struct_missing_field_fails_loudly() {
    let schema = dense_point();
    let result = encode(
        &schema,
        &Value::Struct(vec![(
            1,
            Value::List(vec![Value::Struct(vec![(1, Value::F64(1.0))])]), // y missing
        )]),
        SchemaMode::HashOnly,
    );
    assert!(matches!(result, Err(Error::MissingField(2))));
}

#[test]
fn dense_and_sparse_schemas_have_different_ids() {
    let dense = dense_point();
    let sparse = SchemaBuilder::new()
        .add_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct("Shape", vec![(1, "pts", Dt::list(Dt::named("Point")))])
        .build("Shape")
        .unwrap();
    // Density changes layout, so it must change schema identity.
    assert_ne!(dense.id(), sparse.id());
}

#[test]
fn dense_writer_evolves_into_wider_reader() {
    let writer = dense_point();
    // Reader added a field to Point (sparse in v2) and expects f64 x.
    let reader = SchemaBuilder::new()
        .add_struct(
            "Point",
            vec![(1, "x", Dt::F64), (2, "y", Dt::F64), (3, "z", Dt::F64)],
        )
        .add_struct("Shape", vec![(1, "pts", Dt::list(Dt::named("Point")))])
        .build("Shape")
        .unwrap();
    let bytes = encode(
        &writer,
        &Value::Struct(vec![(
            1,
            Value::List(vec![Value::Struct(vec![
                (1, Value::F64(7.0)),
                (2, Value::F64(8.0)),
            ])]),
        )]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    let resolver = Resolver::new(&writer, &reader).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    let pts = root.get_list(1).unwrap().unwrap();
    match pts.get(0).unwrap() {
        Ref::Struct(p) => {
            // Dense writer fields: always present (no bitmap to consult).
            assert_eq!(p.get_f64(1).unwrap(), Some(7.0));
            assert_eq!(p.get_f64(2).unwrap(), Some(8.0));
            // Reader's new field: absent in old data.
            assert_eq!(p.get_f64(3).unwrap(), None);
        }
        other => panic!("expected struct, got {}", other.kind()),
    }
}

#[test]
fn dense_survives_canonical_roundtrip() {
    let schema = dense_point();
    let recovered = verit::Schema::from_canonical(schema.canonical_bytes()).unwrap();
    assert_eq!(recovered.id(), schema.id());
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(
            1,
            Value::List(vec![Value::Struct(vec![
                (1, Value::F64(1.0)),
                (2, Value::F64(2.0)),
            ])]),
        )]),
        SchemaMode::Inline,
    )
    .unwrap();
    // Self-description still works with dense types in the schema.
    let json = verit::dump_json(&bytes).unwrap();
    assert_eq!(json, r#"{"pts":[{"x":1,"y":2}]}"#);
}