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
//! `#[derive(Verit)]` proofs (behind the `derive` feature): the macro builds
//! the same canonical schema — and therefore the same 128-bit id — as the
//! hand-written `SchemaBuilder`, and its `to_verit` produces byte-for-byte the
//! same bytes as the dynamic encoder over the equivalent `Value`. So a derived
//! Rust type is on the exact same wire as every other language's writer.
#![cfg(feature = "derive")]

use verit::{encode, Dt, Message, Resolver, SchemaBuilder, SchemaMode, Value, Verit, VeritType};

#[derive(Verit, Debug, PartialEq, Clone)]
#[verit(mode = "dense")]
struct Point {
    #[verit(id = 1)]
    x: f64,
    #[verit(id = 2)]
    y: f64,
}

#[derive(Verit, Debug, PartialEq, Clone)]
struct Order {
    #[verit(id = 1)]
    id: u64,
    #[verit(id = 2)]
    item: String,
    #[verit(id = 3)]
    qty: u32,
    #[verit(id = 4)]
    tags: Vec<String>,
    #[verit(id = 5)]
    origin: Point,
    #[verit(id = 6)]
    note: Option<String>,
    #[verit(id = 7)]
    path: Vec<Point>,
    #[verit(id = 8)]
    blob: Vec<u8>,
}

fn sample() -> Order {
    Order {
        id: 42,
        item: "widget".into(),
        qty: 7,
        tags: vec!["a".into(), "bb".into()],
        origin: Point { x: 1.5, y: -2.5 },
        note: Some("gift".into()),
        path: vec![Point { x: 0.0, y: 0.0 }, Point { x: 1.0, y: 1.0 }],
        blob: vec![0xde, 0xad, 0xbe, 0xef],
    }
}

/// The hand-built schema the derive must reproduce, field-for-field.
fn hand_schema() -> verit::Schema {
    SchemaBuilder::new()
        .add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct(
            "Order",
            vec![
                (1, "id", Dt::U64),
                (2, "item", Dt::Str),
                (3, "qty", Dt::U32),
                (4, "tags", Dt::list(Dt::Str)),
                (5, "origin", Dt::named("Point")),
                (6, "note", Dt::Str),
                (7, "path", Dt::list(Dt::named("Point"))),
                (8, "blob", Dt::Bytes),
            ],
        )
        .build("Order")
        .unwrap()
}

#[test]
fn derived_schema_id_matches_hand_built() {
    assert_eq!(
        Order::verit_schema_id(),
        hand_schema().id(),
        "the derive must build the same canonical schema as SchemaBuilder"
    );
    // The nested type roots at itself with its own id.
    let point_only = SchemaBuilder::new()
        .add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .build("Point")
        .unwrap();
    assert_eq!(Point::verit_schema_id(), point_only.id());
}

#[test]
fn derived_bytes_are_identical_to_the_dynamic_encoder() {
    let order = sample();
    let derived = order.to_verit(SchemaMode::HashOnly).unwrap();

    // The equivalent dynamic Value, fields in ascending id order (matching the
    // derive's field declaration order) so heap objects land in the same place.
    let value = Value::Struct(vec![
        (1, Value::U64(42)),
        (2, Value::str("widget")),
        (3, Value::U32(7)),
        (4, Value::List(vec![Value::str("a"), Value::str("bb")])),
        (
            5,
            Value::Struct(vec![(1, Value::F64(1.5)), (2, Value::F64(-2.5))]),
        ),
        (6, Value::str("gift")),
        (
            7,
            Value::List(vec![
                Value::Struct(vec![(1, Value::F64(0.0)), (2, Value::F64(0.0))]),
                Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(1.0))]),
            ]),
        ),
        (8, Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])),
    ]);
    let dynamic = encode(&hand_schema(), &value, SchemaMode::HashOnly).unwrap();

    assert_eq!(
        derived, dynamic,
        "derived to_verit must be byte-identical to the dynamic encoder"
    );
}

#[test]
fn round_trips_through_from_verit() {
    let order = sample();
    for mode in [SchemaMode::HashOnly, SchemaMode::Inline] {
        let bytes = order.to_verit(mode).unwrap();
        let back = Order::from_verit(&bytes).unwrap();
        assert_eq!(order, back, "round-trip under {mode:?}");
    }
}

#[test]
fn absent_option_is_omitted_and_reads_back_none() {
    let mut order = sample();
    order.note = None;
    let bytes = order.to_verit(SchemaMode::HashOnly).unwrap();

    // The reader sees the note field as absent.
    let schema = Order::verit_schema();
    let resolver = Resolver::identity(schema).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    assert_eq!(root.get_str(6).unwrap(), None);

    let back = Order::from_verit(&bytes).unwrap();
    assert_eq!(back.note, None);
}

#[test]
fn inline_mode_is_self_describing() {
    let bytes = sample().to_verit(SchemaMode::Inline).unwrap();
    let json = verit::dump_json(&bytes).unwrap();
    assert!(json.contains("\"item\":\"widget\""), "dump: {json}");
    assert!(
        json.contains("\"origin\":{\"x\":1.5,\"y\":-2.5}"),
        "dump: {json}"
    );
    // The inline schema id round-trips.
    let msg = Message::parse(&bytes).unwrap();
    assert_eq!(msg.schema_id(), Order::verit_schema_id());
}

#[test]
fn nested_list_of_structs_round_trips() {
    let order = sample();
    let back = Order::from_verit(&order.to_verit(SchemaMode::HashOnly).unwrap()).unwrap();
    assert_eq!(back.path.len(), 2);
    assert_eq!(back.path[1], Point { x: 1.0, y: 1.0 });
    assert_eq!(back.blob, vec![0xde, 0xad, 0xbe, 0xef]);
}