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
//! Proof obligation 3: a message plus *nothing else* is fully interpretable.
//! `dump_json` gets only bytes — no schema object, no registry — and must
//! recover structure, field names, and values.

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

#[test]
fn dump_with_zero_prior_knowledge() {
    let schema = SchemaBuilder::new()
        .add_struct("Person", vec![(1, "name", Dt::Str), (2, "age", Dt::U8)])
        .build("Person")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(1, Value::str("Ada")), (2, Value::U8(36))]),
        SchemaMode::Inline,
    )
    .unwrap();

    // From here on, pretend we know nothing but `bytes`.
    let json = dump_json(&bytes).unwrap();
    assert_eq!(json, r#"{"name":"Ada","age":36}"#);
}

#[test]
fn dump_renders_nested_structs_lists_and_enum_names() {
    let schema = SchemaBuilder::new()
        .add_enum("Color", vec![(0, "Red"), (1, "Green"), (2, "Blue")])
        .add_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct(
            "Shape",
            vec![
                (1, "label", Dt::Str),
                (2, "color", Dt::named("Color")),
                (3, "center", Dt::named("Point")),
                (4, "corners", Dt::list(Dt::named("Point"))),
                (5, "checksum", Dt::Bytes),
            ],
        )
        .build("Shape")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![
            (1, Value::str("box")),
            (2, Value::Enum(2)),
            (
                3,
                Value::Struct(vec![(1, Value::F64(0.5)), (2, Value::F64(-1.0))]),
            ),
            (
                4,
                Value::List(vec![
                    Value::Struct(vec![(1, Value::F64(0.0)), (2, Value::F64(0.0))]),
                    Value::Struct(vec![(1, Value::F64(1.0))]),
                ]),
            ),
            (5, Value::Bytes(vec![0xAB, 0xCD])),
        ]),
        SchemaMode::Inline,
    )
    .unwrap();

    let json = dump_json(&bytes).unwrap();
    assert_eq!(
        json,
        r#"{"label":"box","color":"Blue","center":{"x":0.5,"y":-1},"corners":[{"x":0,"y":0},{"x":1}],"checksum":"abcd"}"#
    );
}

#[test]
fn unknown_enum_value_dumps_as_number() {
    let schema = SchemaBuilder::new()
        .add_enum("Color", vec![(0, "Red")])
        .add_struct("S", vec![(1, "c", Dt::named("Color"))])
        .build("S")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(1, Value::Enum(77))]),
        SchemaMode::Inline,
    )
    .unwrap();
    assert_eq!(dump_json(&bytes).unwrap(), r#"{"c":77}"#);
}

#[test]
fn hash_only_message_refuses_dump() {
    let schema = SchemaBuilder::new()
        .add_struct("S", vec![(1, "x", Dt::U32)])
        .build("S")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(1, Value::U32(5))]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    assert!(matches!(dump_json(&bytes), Err(Error::NoInlineSchema)));
}

#[test]
fn inline_schema_recovers_and_hash_verifies() {
    let schema = SchemaBuilder::new()
        .add_struct("S", vec![(1, "x", Dt::U32), (2, "s", Dt::Str)])
        .build("S")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![(1, Value::U32(5))]),
        SchemaMode::Inline,
    )
    .unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let recovered = msg.writer_schema().unwrap().unwrap();
    assert_eq!(recovered.id(), schema.id());
    assert_eq!(recovered.canonical_bytes(), schema.canonical_bytes());
}

#[test]
fn tampered_inline_schema_is_detected() {
    let schema = SchemaBuilder::new()
        .add_struct("S", vec![(1, "x", Dt::U32)])
        .build("S")
        .unwrap();
    let mut bytes = encode(
        &schema,
        &Value::Struct(vec![(1, Value::U32(5))]),
        SchemaMode::Inline,
    )
    .unwrap();
    // Flip a byte inside the inline schema region (header is 32 bytes).
    bytes[36] ^= 0xFF;
    let msg = Message::parse(&bytes).unwrap();
    // Either the schema no longer decodes, or it decodes to something whose
    // content hash disagrees with the header. Both must surface as errors.
    assert!(msg.writer_schema().is_err());
}

#[test]
fn size_overhead_is_bounded_and_amortizable() {
    let schema = SchemaBuilder::new()
        .add_struct("Person", vec![(1, "name", Dt::Str), (2, "age", Dt::U8)])
        .build("Person")
        .unwrap();
    let value = Value::Struct(vec![(1, Value::str("Ada")), (2, Value::U8(36))]);
    let inline = encode(&schema, &value, SchemaMode::Inline).unwrap();
    let hash_only = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
    // Self-description costs exactly the canonical schema (plus ≤7 pad bytes);
    // streams amortize it away by switching to HashOnly.
    let overhead = inline.len() - hash_only.len();
    assert!(overhead >= schema.canonical_bytes().len());
    assert!(overhead < schema.canonical_bytes().len() + 8);
}