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
//! `map<K, V>` conformance (the Rust reference for Phase 3). Maps encode as a
//! count + entry blocks (each a dense 2-field {key, value} struct), entries
//! **sorted by key in canonical order** so one logical map has exactly one
//! byte encoding regardless of insertion order.

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

fn wrap(field_ty: Dt) -> verit::Schema {
    // A root struct with a single map field (id 1).
    SchemaBuilder::new()
        .add_struct("Doc", vec![(1, "m", field_ty)])
        .build("Doc")
        .unwrap()
}

fn map_val(entries: Vec<(Value, Value)>) -> Value {
    Value::Struct(vec![(1, Value::Map(entries))])
}

#[test]
fn string_keyed_map_round_trips_and_reads_in_key_order() {
    let schema = wrap(Dt::map(Dt::Str, Dt::U32));
    let bytes = encode(
        &schema,
        &map_val(vec![
            (Value::str("gamma"), Value::U32(3)),
            (Value::str("alpha"), Value::U32(1)),
            (Value::str("beta"), Value::U32(2)),
        ]),
        SchemaMode::Inline,
    )
    .unwrap();

    let resolver = Resolver::identity(&schema).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    let m = root.get_map(1).unwrap().unwrap();
    assert_eq!(m.len(), 3);
    // Canonical order: keys ascending by UTF-8 bytes.
    let got: Vec<(String, u32)> = m
        .iter()
        .map(|e| {
            let (k, v) = e.unwrap();
            match (k, v) {
                (Ref::Str(k), Ref::U32(v)) => (k.to_string(), v),
                _ => panic!("wrong ref types"),
            }
        })
        .collect();
    assert_eq!(
        got,
        vec![("alpha".into(), 1), ("beta".into(), 2), ("gamma".into(), 3)]
    );

    // Self-describing: dump renders a JSON object with ordered keys.
    assert_eq!(
        dump_json(&bytes).unwrap(),
        r#"{"m":{"alpha":1,"beta":2,"gamma":3}}"#
    );
}

#[test]
fn insertion_order_does_not_affect_bytes() {
    let schema = wrap(Dt::map(Dt::U16, Dt::Str));
    let a = encode(
        &schema,
        &map_val(vec![
            (Value::U16(10), Value::str("ten")),
            (Value::U16(2), Value::str("two")),
            (Value::U16(7), Value::str("seven")),
        ]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    let b = encode(
        &schema,
        &map_val(vec![
            (Value::U16(7), Value::str("seven")),
            (Value::U16(10), Value::str("ten")),
            (Value::U16(2), Value::str("two")),
        ]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    assert_eq!(
        a, b,
        "canonical key ordering makes encoding order-independent"
    );
}

#[test]
fn duplicate_keys_are_rejected() {
    let schema = wrap(Dt::map(Dt::Str, Dt::U8));
    let err = encode(
        &schema,
        &map_val(vec![
            (Value::str("k"), Value::U8(1)),
            (Value::str("k"), Value::U8(2)),
        ]),
        SchemaMode::HashOnly,
    )
    .unwrap_err();
    assert!(matches!(err, Error::DuplicateMapKey));
}

#[test]
fn map_of_struct_values() {
    let schema = SchemaBuilder::new()
        .add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct("Doc", vec![(1, "m", Dt::map(Dt::Str, Dt::named("Point")))])
        .build("Doc")
        .unwrap();
    let bytes = encode(
        &schema,
        &map_val(vec![
            (
                Value::str("origin"),
                Value::Struct(vec![(1, Value::F64(0.0)), (2, Value::F64(0.0))]),
            ),
            (
                Value::str("unit"),
                Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(1.0))]),
            ),
        ]),
        SchemaMode::Inline,
    )
    .unwrap();
    assert_eq!(
        dump_json(&bytes).unwrap(),
        r#"{"m":{"origin":{"x":0,"y":0},"unit":{"x":1,"y":1}}}"#
    );

    // The bounded read surface must also traverse maps cleanly.
    let resolver = Resolver::identity(&schema).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let budget = Budget::new(msg.suggested_budget());
    msg.verify(&resolver, &budget).unwrap();
}

#[test]
fn map_value_widens_under_evolution_but_key_change_is_incompatible() {
    let writer = wrap(Dt::map(Dt::Str, Dt::U16));
    let bytes = encode(
        &writer,
        &map_val(vec![(Value::str("n"), Value::U16(60000))]),
        SchemaMode::HashOnly,
    )
    .unwrap();

    // Reader widened the value u16 -> u32: allowed.
    let reader = wrap(Dt::map(Dt::Str, Dt::U32));
    let resolver = Resolver::new(&writer, &reader).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    let m = root.get_map(1).unwrap().unwrap();
    let (k, v) = m.get(0).unwrap();
    assert!(matches!(k, Ref::Str("n")));
    assert!(matches!(v, Ref::U32(60000)));

    // Changing the key type is rejected at resolve time (could reorder/collide).
    let bad_reader = wrap(Dt::map(Dt::U32, Dt::U32));
    assert!(matches!(
        Resolver::new(&writer, &bad_reader),
        Err(Error::Incompatible(_))
    ));
}

#[test]
fn nested_and_listed_maps_round_trip() {
    // map<string, map<u8, u8>> and list<map<...>> exercise recursion.
    let schema = wrap(Dt::map(Dt::Str, Dt::map(Dt::U8, Dt::U8)));
    let bytes = encode(
        &schema,
        &map_val(vec![(
            Value::str("outer"),
            Value::Map(vec![
                (Value::U8(2), Value::U8(20)),
                (Value::U8(1), Value::U8(10)),
            ]),
        )]),
        SchemaMode::Inline,
    )
    .unwrap();
    assert_eq!(
        dump_json(&bytes).unwrap(),
        r#"{"m":{"outer":{"1":10,"2":20}}}"#
    );
}

#[test]
fn invalid_key_type_is_rejected_at_build() {
    // Floats are not comparable keys (NaN); the schema must refuse to build.
    assert!(SchemaBuilder::new()
        .add_struct("Doc", vec![(1, "m", Dt::map(Dt::F64, Dt::U8))])
        .build("Doc")
        .is_err());
    // bytes/struct/list keys likewise.
    assert!(SchemaBuilder::new()
        .add_struct("Doc", vec![(1, "m", Dt::map(Dt::Bytes, Dt::U8))])
        .build("Doc")
        .is_err());
}

#[test]
fn empty_map_round_trips() {
    let schema = wrap(Dt::map(Dt::Str, Dt::U8));
    let bytes = encode(&schema, &map_val(vec![]), SchemaMode::Inline).unwrap();
    assert_eq!(dump_json(&bytes).unwrap(), r#"{"m":{}}"#);
    let resolver = Resolver::identity(&schema).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    assert_eq!(root.get_map(1).unwrap().unwrap().len(), 0);
}

#[test]
fn idl_parses_map_syntax() {
    let schema = verit::idl::parse("struct Doc { 1: m map<string, u32> } root Doc").unwrap();
    let hand = wrap(Dt::map(Dt::Str, Dt::U32));
    assert_eq!(schema.id(), hand.id());
}