use std::fs;
use std::path::Path;
use verit::{
dump_json, encode, Dt, Message, Ref, Resolver, Schema, SchemaBuilder, SchemaMode, StructReader,
Type, Value,
};
fn corpus() -> Vec<(&'static str, Schema, Value)> {
let mut cases = Vec::new();
let scalars = SchemaBuilder::new()
.add_struct(
"Rec",
vec![
(1, "flag", Dt::Bool),
(2, "n", Dt::U32),
(3, "big", Dt::U64),
(4, "sign", Dt::I32),
(5, "ratio", Dt::F64),
(6, "name", Dt::Str),
(7, "blob", Dt::Bytes),
],
)
.build("Rec")
.unwrap();
cases.push((
"scalars",
scalars,
Value::Struct(vec![
(1, Value::Bool(true)),
(2, Value::U32(42)),
(3, Value::U64(0xFFFF_FFFF_0000)),
(4, Value::I32(-12345)),
(5, Value::F64(2.5)),
(6, Value::str("veritate")),
(7, Value::Bytes(vec![0xDE, 0xAD])),
]),
));
let nested = SchemaBuilder::new()
.add_enum("Level", vec![(0, "Debug"), (1, "Info"), (2, "Error")])
.add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
.add_struct(
"Doc",
vec![
(1, "title", Dt::Str),
(2, "level", Dt::named("Level")),
(3, "origin", Dt::named("Point")),
(4, "path", Dt::list(Dt::named("Point"))),
(5, "tags", Dt::list(Dt::Str)),
],
)
.build("Doc")
.unwrap();
cases.push((
"nested",
nested,
Value::Struct(vec![
(1, Value::str("doc")),
(2, Value::Enum(2)),
(
3,
Value::Struct(vec![(1, Value::F64(1.5)), (2, Value::F64(-2.5))]),
),
(
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)), (2, Value::F64(1.0))]),
]),
),
(5, Value::List(vec![Value::str("a"), Value::str("bb")])),
]),
));
let packed = SchemaBuilder::new()
.add_packed_struct(
"Wide",
vec![
(1, "a", Dt::U8),
(2, "b", Dt::U64),
(3, "c", Dt::Str),
(4, "d", Dt::U32),
(5, "e", Dt::Bool),
(6, "f", Dt::F64),
],
)
.build("Wide")
.unwrap();
cases.push((
"packed",
packed,
Value::Struct(vec![
(2, Value::U64(9)),
(3, Value::str("packed")),
(6, Value::F64(3.25)),
]),
));
let nestedlists = SchemaBuilder::new()
.add_struct(
"M",
vec![
(1, "grid", Dt::list(Dt::list(Dt::U16))),
(2, "words", Dt::list(Dt::Str)),
],
)
.build("M")
.unwrap();
cases.push((
"nestedlists",
nestedlists,
Value::Struct(vec![
(
1,
Value::List(vec![
Value::List(vec![Value::U16(1), Value::U16(2), Value::U16(3)]),
Value::List(vec![]),
Value::List(vec![Value::U16(9)]),
]),
),
(
2,
Value::List(vec![Value::str("x"), Value::str("yy"), Value::str("zzz")]),
),
]),
));
let maps = SchemaBuilder::new()
.add_struct(
"Maps",
vec![
(1, "labels", Dt::map(Dt::U32, Dt::Str)),
(2, "ratios", Dt::map(Dt::Str, Dt::F64)),
],
)
.build("Maps")
.unwrap();
cases.push((
"maps",
maps,
Value::Struct(vec![
(
1,
Value::Map(vec![
(Value::U32(10), Value::str("ten")),
(Value::U32(2), Value::str("two")),
(Value::U32(7), Value::str("seven")),
]),
),
(
2,
Value::Map(vec![
(Value::str("half"), Value::F64(0.5)),
(Value::str("quarter"), Value::F64(0.25)),
]),
),
]),
));
let unions = SchemaBuilder::new()
.add_dense_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
.add_struct(
"Variants",
vec![
(1, "a", Dt::union(vec![Dt::U32, Dt::Str])),
(2, "b", Dt::union(vec![Dt::named("Point"), Dt::Bool])),
],
)
.build("Variants")
.unwrap();
cases.push((
"unions",
unions,
Value::Struct(vec![
(1, Value::Union(1, Box::new(Value::str("hi")))),
(
2,
Value::Union(
0,
Box::new(Value::Struct(vec![
(1, Value::F64(1.5)),
(2, Value::F64(-2.5)),
])),
),
),
]),
));
let defaults = SchemaBuilder::new()
.add_struct("Cfg", vec![(1, "retries", Dt::U32), (2, "name", Dt::Str)])
.set_default("Cfg", 1, Value::U32(3))
.build("Cfg")
.unwrap();
cases.push((
"defaults",
defaults,
Value::Struct(vec![(2, Value::str("svc"))]),
));
let empty = SchemaBuilder::new()
.add_struct("E", vec![(1, "a", Dt::U32), (2, "b", Dt::Str)])
.build("E")
.unwrap();
cases.push(("empty", empty, Value::Struct(vec![])));
cases
}
fn materialize_struct(r: &StructReader) -> Value {
let mut fields = Vec::new();
for f in &r.struct_def().fields {
if let Some(v) = r.get(f.id).unwrap() {
fields.push((f.id, materialize(&f.ty, &v)));
}
}
Value::Struct(fields)
}
fn materialize(ty: &Type, v: &Ref) -> Value {
match v {
Ref::Bool(x) => Value::Bool(*x),
Ref::U8(x) => Value::U8(*x),
Ref::U16(x) => Value::U16(*x),
Ref::U32(x) => Value::U32(*x),
Ref::U64(x) => Value::U64(*x),
Ref::I8(x) => Value::I8(*x),
Ref::I16(x) => Value::I16(*x),
Ref::I32(x) => Value::I32(*x),
Ref::I64(x) => Value::I64(*x),
Ref::F32(x) => Value::F32(*x),
Ref::F64(x) => Value::F64(*x),
Ref::Str(s) => Value::Str(s.to_string()),
Ref::Bytes(b) => Value::Bytes(b.to_vec()),
Ref::Enum(e) => Value::Enum(*e),
Ref::Struct(s) => materialize_struct(s),
Ref::List(l) => {
let elem = match ty {
Type::List(e) => e.as_ref(),
_ => panic!("list value with non-list type"),
};
let mut items = Vec::new();
for i in 0..l.len() {
items.push(materialize(elem, &l.get(i).unwrap()));
}
Value::List(items)
}
Ref::Map(m) => {
let (kty, vty) = match ty {
Type::Map(k, v) => (k.as_ref(), v.as_ref()),
_ => panic!("map value with non-map type"),
};
let mut entries = Vec::new();
for i in 0..m.len() {
let (k, v) = m.get(i).unwrap();
entries.push((materialize(kty, &k), materialize(vty, &v)));
}
Value::Map(entries)
}
Ref::Union(u) => {
let variants = match ty {
Type::Union(vs) => vs,
_ => panic!("union value with non-union type"),
};
let tag = u.tag();
let inner = materialize(&variants[tag as usize], &u.value().unwrap());
Value::Union(tag, Box::new(inner))
}
}
}
fn emit(dir: &str) {
let dir = Path::new(dir);
fs::create_dir_all(dir).unwrap();
let mut manifest = String::new();
for (name, schema, value) in corpus() {
let bytes = encode(&schema, &value, SchemaMode::Inline).unwrap();
fs::write(dir.join(format!("{name}.bin")), &bytes).unwrap();
let dump = dump_json(&bytes).unwrap();
manifest.push_str(&format!("{name}\t{:032x}\t{dump}\n", schema.id()));
}
fs::write(dir.join("manifest.tsv"), manifest).unwrap();
println!(
"rust: emitted {} cases to {}",
corpus().len(),
dir.display()
);
}
fn verify(dir: &str) -> bool {
let dir = Path::new(dir);
let manifest = fs::read_to_string(dir.join("manifest.tsv")).unwrap();
let mut pass = 0;
let mut fail = 0;
for line in manifest.lines() {
let mut it = line.splitn(3, '\t');
let name = it.next().unwrap();
let id_hex = it.next().unwrap();
let want_dump = it.next().unwrap();
let bytes = fs::read(dir.join(format!("{name}.bin"))).unwrap();
let mut errs: Vec<String> = Vec::new();
let expected_id = u128::from_str_radix(id_hex, 16).unwrap();
match Message::parse(&bytes) {
Ok(msg) => {
if msg.schema_id() != expected_id {
errs.push("envelope schema id != manifest".into());
}
match msg.writer_schema() {
Ok(Some(schema)) => {
if schema.id() != expected_id {
errs.push("re-derived schema id != manifest".into());
}
let resolver = Resolver::identity(&schema).unwrap();
let root = msg.root(&resolver).unwrap();
let value = materialize_struct(&root);
let reenc = encode(&schema, &value, SchemaMode::Inline).unwrap();
if reenc != bytes {
errs.push(format!(
"transcode not byte-identical ({} vs {} bytes)",
reenc.len(),
bytes.len()
));
}
match dump_json(&bytes) {
Ok(d) if d == want_dump => {}
Ok(d) => errs.push(format!("dump mismatch: {d} != {want_dump}")),
Err(e) => errs.push(format!("dump error: {e}")),
}
}
Ok(None) => errs.push("no inline schema".into()),
Err(e) => errs.push(format!("schema decode error: {e}")),
}
}
Err(e) => errs.push(format!("parse error: {e}")),
}
if errs.is_empty() {
pass += 1;
println!("rust verify: {name:<12} PASS");
} else {
fail += 1;
println!("rust verify: {name:<12} FAIL — {}", errs.join("; "));
}
}
println!("rust verify: {pass} passed, {fail} failed");
fail == 0
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let usage = || {
eprintln!("usage: interop <emit|verify> <dir>");
std::process::exit(2);
};
if args.len() != 3 {
usage();
}
match args[1].as_str() {
"emit" => emit(&args[2]),
"verify" => {
if !verify(&args[2]) {
std::process::exit(1);
}
}
_ => usage(),
}
}