use verit::{encode, Dt, Error, Message, Ref, Resolver, Schema, SchemaBuilder, SchemaMode, Value};
fn kitchen_schema() -> 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(
"Everything",
vec![
(1, "b", Dt::Bool),
(2, "u8v", Dt::U8),
(3, "u16v", Dt::U16),
(4, "u32v", Dt::U32),
(5, "u64v", Dt::U64),
(6, "i8v", Dt::I8),
(7, "i16v", Dt::I16),
(8, "i32v", Dt::I32),
(9, "i64v", Dt::I64),
(10, "f32v", Dt::F32),
(11, "f64v", Dt::F64),
(12, "name", Dt::Str),
(13, "blob", Dt::Bytes),
(14, "color", Dt::named("Color")),
(15, "origin", Dt::named("Point")),
(16, "nums", Dt::list(Dt::U32)),
(17, "tags", Dt::list(Dt::Str)),
(18, "points", Dt::list(Dt::named("Point"))),
(19, "matrix", Dt::list(Dt::list(Dt::U8))),
],
)
.build("Everything")
.unwrap()
}
fn everything() -> Value {
Value::Struct(vec![
(1, Value::Bool(true)),
(2, Value::U8(200)),
(3, Value::U16(60_000)),
(4, Value::U32(4_000_000_000)),
(5, Value::U64(u64::MAX - 1)),
(6, Value::I8(-100)),
(7, Value::I16(-30_000)),
(8, Value::I32(-2_000_000_000)),
(9, Value::I64(i64::MIN + 1)),
(10, Value::F32(3.5)),
(11, Value::F64(-2.25)),
(12, Value::str("héllo wörld 🚀")),
(13, Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])),
(14, Value::Enum(2)),
(
15,
Value::Struct(vec![(1, Value::F64(1.5)), (2, Value::F64(-0.5))]),
),
(
16,
Value::List(vec![Value::U32(1), Value::U32(2), Value::U32(3)]),
),
(
17,
Value::List(vec![Value::str("a"), Value::str(""), Value::str("ccc")]),
),
(
18,
Value::List(vec![
Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(2.0))]),
Value::Struct(vec![(1, Value::F64(3.0))]), ]),
),
(
19,
Value::List(vec![
Value::List(vec![Value::U8(1), Value::U8(2)]),
Value::List(vec![]),
]),
),
])
}
#[test]
fn roundtrip_every_type() {
let schema = kitchen_schema();
let bytes = encode(&schema, &everything(), SchemaMode::HashOnly).unwrap();
let msg = Message::parse(&bytes).unwrap();
assert_eq!(msg.schema_id(), schema.id());
assert!(!msg.has_inline_schema());
let resolver = Resolver::identity(&schema).unwrap();
let root = msg.root(&resolver).unwrap();
assert_eq!(root.get_bool(1).unwrap(), Some(true));
assert_eq!(root.get_u8(2).unwrap(), Some(200));
assert_eq!(root.get_u16(3).unwrap(), Some(60_000));
assert_eq!(root.get_u32(4).unwrap(), Some(4_000_000_000));
assert_eq!(root.get_u64(5).unwrap(), Some(u64::MAX - 1));
assert_eq!(root.get_i8(6).unwrap(), Some(-100));
assert_eq!(root.get_i16(7).unwrap(), Some(-30_000));
assert_eq!(root.get_i32(8).unwrap(), Some(-2_000_000_000));
assert_eq!(root.get_i64(9).unwrap(), Some(i64::MIN + 1));
assert_eq!(root.get_f32(10).unwrap(), Some(3.5));
assert_eq!(root.get_f64(11).unwrap(), Some(-2.25));
assert_eq!(root.get_str(12).unwrap(), Some("héllo wörld 🚀"));
assert_eq!(
root.get_bytes(13).unwrap(),
Some(&[0xDE, 0xAD, 0xBE, 0xEF][..])
);
assert_eq!(root.get_enum(14).unwrap(), Some(2));
let origin = root.get_struct(15).unwrap().unwrap();
assert_eq!(origin.get_f64(1).unwrap(), Some(1.5));
assert_eq!(origin.get_f64(2).unwrap(), Some(-0.5));
let nums = root.get_list(16).unwrap().unwrap();
assert_eq!(nums.len(), 3);
assert!(matches!(nums.get(0).unwrap(), Ref::U32(1)));
assert!(matches!(nums.get(2).unwrap(), Ref::U32(3)));
assert!(matches!(nums.get(3), Err(Error::IndexOutOfBounds)));
let tags = root.get_list(17).unwrap().unwrap();
let collected: Vec<&str> = tags
.iter()
.map(|r| match r.unwrap() {
Ref::Str(s) => s,
other => panic!("expected string element, got {}", other.kind()),
})
.collect();
assert_eq!(collected, vec!["a", "", "ccc"]);
let points = root.get_list(18).unwrap().unwrap();
assert_eq!(points.len(), 2);
let p0 = match points.get(0).unwrap() {
Ref::Struct(s) => s,
other => panic!("expected struct element, got {}", other.kind()),
};
assert_eq!(p0.get_f64(1).unwrap(), Some(1.0));
assert_eq!(p0.get_f64(2).unwrap(), Some(2.0));
let p1 = match points.get(1).unwrap() {
Ref::Struct(s) => s,
other => panic!("expected struct element, got {}", other.kind()),
};
assert_eq!(p1.get_f64(1).unwrap(), Some(3.0));
assert_eq!(p1.get_f64(2).unwrap(), None, "absent field in list element");
let matrix = root.get_list(19).unwrap().unwrap();
assert_eq!(matrix.len(), 2);
let row0 = match matrix.get(0).unwrap() {
Ref::List(l) => l,
other => panic!("expected list element, got {}", other.kind()),
};
assert_eq!(row0.len(), 2);
assert!(matches!(row0.get(1).unwrap(), Ref::U8(2)));
let row1 = match matrix.get(1).unwrap() {
Ref::List(l) => l,
other => panic!("expected list element, got {}", other.kind()),
};
assert_eq!(row1.len(), 0);
assert!(row1.is_empty());
}
#[test]
fn absent_fields_read_none() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(4, Value::U32(7))]);
let bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
let msg = Message::parse(&bytes).unwrap();
let resolver = Resolver::identity(&schema).unwrap();
let root = msg.root(&resolver).unwrap();
assert_eq!(root.get_u32(4).unwrap(), Some(7));
assert_eq!(root.get_bool(1).unwrap(), None);
assert_eq!(root.get_str(12).unwrap(), None);
assert_eq!(root.get_struct(15).unwrap().map(|_| ()), None);
assert_eq!(root.get_list(16).unwrap().map(|_| ()), None);
}
#[test]
fn open_enum_roundtrips_unknown_values() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(14, Value::Enum(999))]);
let bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
let msg = Message::parse(&bytes).unwrap();
let resolver = Resolver::identity(&schema).unwrap();
let root = msg.root(&resolver).unwrap();
assert_eq!(root.get_enum(14).unwrap(), Some(999));
}
#[test]
fn unknown_field_id_fails_encode() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(99, Value::U32(1))]);
assert!(matches!(
encode(&schema, &value, SchemaMode::HashOnly),
Err(Error::UnknownFieldId(99))
));
}
#[test]
fn duplicate_field_fails_encode() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(4, Value::U32(1)), (4, Value::U32(2))]);
assert!(matches!(
encode(&schema, &value, SchemaMode::HashOnly),
Err(Error::DuplicateField(4))
));
}
#[test]
fn type_mismatch_fails_encode() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(12, Value::U32(1))]); assert!(matches!(
encode(&schema, &value, SchemaMode::HashOnly),
Err(Error::TypeMismatch { .. })
));
}
#[test]
fn garbage_input_is_rejected_not_panicked() {
assert!(matches!(Message::parse(b"nope"), Err(Error::Truncated)));
assert!(matches!(
Message::parse(b"XXXX0123456789abcdef0123456789ab"),
Err(Error::BadMagic)
));
let schema = kitchen_schema();
let value = Value::Struct(vec![(12, Value::str("hi"))]);
let mut bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
bytes[24..28].copy_from_slice(&u32::MAX.to_le_bytes()); let msg = Message::parse(&bytes).unwrap();
let resolver = Resolver::identity(&schema).unwrap();
let root = msg.root(&resolver).unwrap();
assert!(root.get_str(12).is_err());
}
#[test]
fn version_and_header_are_enforced() {
let schema = kitchen_schema();
let value = Value::Struct(vec![(12, Value::str("hi"))]);
let good = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
assert!(Message::parse(&good).is_ok());
let mut wrong_ver = good.clone();
wrong_ver[3] = b'3';
assert!(matches!(
Message::parse(&wrong_ver),
Err(Error::UnsupportedVersion {
found: b'3',
supported: b'2'
})
));
let mut not_vrt = good.clone();
not_vrt[0..3].copy_from_slice(b"ZZZ");
assert!(matches!(Message::parse(¬_vrt), Err(Error::BadMagic)));
let mut bad_flag = good.clone();
bad_flag[4] = 0b0000_0010; assert!(matches!(
Message::parse(&bad_flag),
Err(Error::MalformedHeader(_))
));
let mut bad_reserved = good.clone();
bad_reserved[6] = 1;
assert!(matches!(
Message::parse(&bad_reserved),
Err(Error::MalformedHeader(_))
));
}