Fix M6, extend M4 coverage over aligned/legacy-walk/reader paths (review #006)

- M6 (new finding, fixed): the legacy packed BAST-walker's field-disc
  union arm materialized only the discriminator field and started the
  variant immediately after it — silently reading the remaining shared
  fields' bytes as variant data whenever the union had any (probe:
  record<union> values produced {"handle": 5} where 5 was seq's value).
  The arm now walks all shared fields in order and starts the variant
  after the whole shared walk, matching H3's convention and the plan
  materializer's object shape (__discriminator + typed disc value +
  shared + variant). Reachable via aligned record/leaf paths only.
- M4 item 1: aligned-materializer test family — nested-struct
  recursion (3-level, previously 0 executions), maxLength trim in
  nested structs, invalid-UTF-8 Access error, offset-indirect
  out-of-bounds + data-after-sibling roundtrip, and records with
  struct/array/byte-disc-union/field-disc-union/wide-primitive values
  driving the legacy walker's previously-dead arms.
- M4 item 2: reader coverage — field-disc uint16/uint32/enum arms,
  byte-disc uint16/uint32 arms, nested-union variant size walk, and
  the public schema()/plan() accessors (all previously 0-execution).
- M4 item 3: data_access indirect-write tests at nonzero pair offset +
  data-region bounds refusal (the u32-truncation guards themselves
  need >4GiB slices and stay documented as defensively unreachable
  on 64-bit).
- M4 item 4 follow-through: OQ-001 rejection for struct elements and
  endian propagation for fixed elements locked with offset-map tests;
  the dead composite arms were removed in the previous commit.

Coverage after: materialize.rs 64.48→85.72% lines, TOTAL 89.59→90.60%.
Verified: 567 tests green (463 crate + 17 + 34 + 15 + 12 + 2 ignored),
clippy -D warnings clean, wasm build green, cargo doc zero warnings.
This commit is contained in:
glm-5.3-flash committed 2026-09-02 20:26:21 +00:00
1 parent 5f9793f9c0
commit 2eb086f400
3 files changed
+734 -12

No files matched your search

+37
View File
@@ -712,6 +712,43 @@ mod tests {
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
fn m4_write_bytes_indirect_pair_at_nonzero_offset_data_ok() {
// The indirect write's pair offset and data offset are
// independent; pin the pair landing mid-buffer with the data
// elsewhere (review #006 M4 item 3 — the write_bytes_indirect
// guard family is the canonical overflow-guard pattern, review
// #002 M2, and its nonzero-offset paths were uncovered).
let mut buf = vec![0u8; 24];
let written = write_bytes_indirect(&mut buf, 4, 16, b"xyz", "blob", BE).unwrap();
assert_eq!(written, 8);
assert_eq!(&buf[4..8], &16u32.to_be_bytes());
assert_eq!(&buf[8..12], &3u32.to_be_bytes());
assert_eq!(&buf[16..19], b"xyz");
let bytes = read_bytes_indirect(&buf, 4, "blob", BE).unwrap();
assert_eq!(bytes, b"xyz");
}
#[test]
fn m4_write_bytes_indirect_data_length_exceeds_bounds() {
// The data-region bounds check in write_bytes_indirect: the pair
// fits, but the {data_offset, length} target runs past the buffer
// end — the write must refuse without corrupting the pair.
let mut buf = vec![0u8; 20];
let err = write_bytes_indirect(&mut buf, 0, 16, b"hello", "blob", LE).unwrap_err();
match err {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, "blob");
assert!(reason.contains("bounds"), "reason: {reason}");
}
other => panic!("expected Access, got {other:?}"),
}
// The pair was already written (offset+length at 0..8) — that's
// fine; the error refuses the data copy only.
assert_eq!(&buf[0..4], &16u32.to_le_bytes());
assert_eq!(&buf[4..8], &5u32.to_le_bytes());
}
#[test]
fn read_at_nonzero_offset() {
let mut buf = vec![0u8; 16];
+444 -12
View File
@@ -727,18 +727,42 @@ fn materialize_union_packed(
}
BastDiscriminator::Field { name } => {
let fields = union_node.fields();
let disc_field = fields
.iter()
.find(|f| f.name() == *name)
.ok_or_else(|| {
AlkTypeError::Schema(format!(
"materialize: union at {field_path} has no discriminator field '{name}'"
))
})?;
let disc_path = format!("{field_path}.{name}");
let disc_value =
materialize_field_packed(doc, disc_field, &disc_path, endian, buffer, offset)?;
let key = union_discriminator_key(&disc_value, field_path)?;
if fields.is_empty() {
return Err(AlkTypeError::Schema(format!(
"materialize: union at {field_path} has no shared fields (the \
discriminator field must be declared first)"
)));
}
// The shared-then-variant wire convention (H3): walk ALL
// declared `fields` in order — capturing the disc value at its
// real position — and start the variant after the whole shared
// walk. (Review #006 M6: the pre-fix arm materialized only the
// disc field and started the variant immediately after it,
// silently reading the remaining shared fields' bytes as
// variant data whenever the union had any.)
let mut key = None;
let mut disc_value: Option<Value> = None;
let mut shared_values: Vec<(String, Value)> = Vec::new();
for field in fields {
let path = format!("{field_path}.{}", field.name());
let value = materialize_field_packed(doc, field, &path, endian, buffer, offset)?;
if field.name() == name.as_str() {
key = Some(union_discriminator_key(&value, field_path)?);
disc_value = Some(value);
} else {
shared_values.push((field.name().to_string(), value));
}
}
let key = key.ok_or_else(|| {
AlkTypeError::Schema(format!(
"materialize: union at {field_path} has no discriminator field '{name}'"
))
})?;
let disc_value = disc_value.ok_or_else(|| {
AlkTypeError::Schema(format!(
"internal: union {field_path} discriminator field {name:?} not walked"
))
})?;
let mapping = union_node.mapping();
let variant_ty = mapping
.iter()
@@ -760,6 +784,9 @@ fn materialize_union_packed(
let mut obj = Map::new();
obj.insert(DISCRIMINATOR_KEY.to_string(), Value::String(key.clone()));
obj.insert((*name).to_string(), disc_value);
for (k, v) in shared_values {
obj.insert(k, v);
}
flatten_variant_into(&mut obj, variant_value);
Ok(Value::Object(obj))
}
@@ -1029,6 +1056,7 @@ fn materialize_leaf_at(
#[cfg(test)]
mod tests {
use super::*;
use crate::offset_map::ByteRange;
use serde_json::json;
fn doc_from(root: &Value, name: &str) -> BastDoc {
@@ -1648,6 +1676,410 @@ mod tests {
assert_eq!(v["crc"], json!(0x01020304u32));
}
// ----- M4 item 1: aligned nested structs + error arms ----------------
#[test]
fn m4_aligned_nested_struct_recursion_roundtrips() {
// The nested-struct recursion (materialize_struct_aligned entered
// with a non-empty path_prefix) had 0 executions in the review's
// coverage run — the heart of aligned validate_bytes was never
// exercised beyond a flat 2-field struct. This drives a
// three-level nesting through OffsetMap + materialize_aligned.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "header",
"kind": {
"kind": "struct",
"fields": [
{ "name": "magic", "kind": "uint32" },
{
"name": "meta",
"kind": {
"kind": "struct",
"fields": [
{ "name": "ver", "kind": "uint16" },
{ "name": "flag", "kind": "uint8" }
]
}
}
]
}
},
{ "name": "body", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
// Layout: header.magic @0..4, header.meta.ver @4..6, flag @6..7,
// (pad 1) body @8..12 — total 12.
assert_eq!(map.get("header.magic").map(|e| e.range), Some(ByteRange { start: 0, end: 4 }));
assert_eq!(map.get("header.meta.ver").map(|e| e.range), Some(ByteRange { start: 4, end: 6 }));
assert_eq!(map.get("header.meta.flag").map(|e| e.range), Some(ByteRange { start: 6, end: 7 }));
assert_eq!(map.get("body").map(|e| e.range), Some(ByteRange { start: 8, end: 12 }));
assert_eq!(map.total_size(), 12);
let mut buf = vec![0u8; 12];
buf[0..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
buf[4..6].copy_from_slice(&7u16.to_le_bytes());
buf[6] = 1;
buf[8..12].copy_from_slice(&99u32.to_le_bytes());
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["header"]["magic"], json!(0xDEADBEEFu32));
assert_eq!(v["header"]["meta"]["ver"], json!(7));
assert_eq!(v["header"]["meta"]["flag"], json!(1));
assert_eq!(v["body"], json!(99));
}
#[test]
fn m4_aligned_nested_struct_maxlength_string_deep() {
// maxLength trim inside nested aligned structs — the trim arm is
// shared with the flat case, but the nested entry lookup is not.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "inner",
"kind": {
"kind": "struct",
"fields": [
{ "name": "name", "kind": "string", "maxLength": 8 }
]
}
},
{ "name": "id", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 12];
buf[..5].copy_from_slice(b"hello");
buf[8..12].copy_from_slice(&5u32.to_le_bytes());
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["inner"]["name"], json!("hello"));
assert_eq!(v["id"], json!(5));
}
#[test]
fn m4_aligned_max_length_string_invalid_utf8_is_access_error() {
// The UTF-8 error arm of the maxLength trim (0-execution in the
// review's coverage run): garbage bytes in the reservation must
// be a clean Access error, not a panic or a silent String.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "name", "kind": "string", "maxLength": 8 }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8];
buf[..4].copy_from_slice(&[0xFF, 0xFE, 0x00, 0x80]);
let err = materialize_aligned_strict(&doc, &buf).unwrap_err();
match err {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, "name");
assert!(reason.contains("UTF-8"), "reason: {reason}");
}
other => panic!("expected Access, got {other:?}"),
}
}
#[test]
fn m4_aligned_offset_indirect_out_of_bounds_is_access_error() {
// The offset-indirect arms' error path: the {offset, length} pair
// points outside the buffer — data_access's bounds check rejects.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "name", "kind": "string", "encoding": "offset-indirect" }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&1000u32.to_le_bytes());
buf[4..8].copy_from_slice(&4u32.to_le_bytes());
let err = materialize_aligned_strict(&doc, &buf).unwrap_err();
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn m4_aligned_offset_indirect_string_roundtrips_after_sibling() {
// Offset-indirect with a following field — the indirect pair's
// data can point anywhere in the buffer (that is the point of
// the encoding); pin the data landing after the sibling field.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "id", "kind": "uint32" },
{ "name": "name", "kind": "string", "encoding": "offset-indirect" },
{ "name": "tail", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
// Layout: id @0..4, pair @4..12, tail @12..16 (total 16);
// string data at 16..21.
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
assert_eq!(map.get("name").map(|e| e.range), Some(ByteRange { start: 4, end: 12 }));
assert_eq!(map.get("tail").map(|e| e.range), Some(ByteRange { start: 12, end: 16 }));
let mut buf = vec![0u8; 21];
buf[0..4].copy_from_slice(&3u32.to_le_bytes());
buf[4..8].copy_from_slice(&16u32.to_le_bytes());
buf[8..12].copy_from_slice(&5u32.to_le_bytes());
buf[12..16].copy_from_slice(&7u32.to_le_bytes());
buf[16..21].copy_from_slice(b"hello");
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["id"], json!(3));
assert_eq!(v["name"], json!("hello"));
assert_eq!(v["tail"], json!(7));
}
#[test]
fn m4_aligned_array_of_fixed_struct_elements_materializes() {
// Struct elements are rejected in aligned mode (their kind is
// `Struct`, which is_fixed_size() refuses — OQ-001's conservative
// gate; the packed reader supports fixed-struct arrays via the
// plan's computed stride but the aligned map does not). Pin the
// aligned array walk over scalar fixed elements + the map's
// per-element entries, which is the aligned-mode shape.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "points",
"kind": {
"kind": "array",
"element": "uint32",
"count": 2
}
}
]
}
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
assert_eq!(map.get("points[0]").map(|e| e.range), Some(ByteRange { start: 0, end: 4 }));
assert_eq!(map.get("points[1]").map(|e| e.range), Some(ByteRange { start: 4, end: 8 }));
let buf = [1u8, 0, 0, 0, 2, 0, 0, 0];
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["points"], json!([1, 2]));
}
#[test]
fn m4_aligned_record_with_struct_values_walks_nested_fields() {
// A record whose values are a struct: the aligned record arm
// drops into the packed BAST walker (materialize_typeref_packed →
// materialize_struct_packed), which was 0-execution in the review's
// coverage run. Pin the recursion + wire arithmetic.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "meta", "kind": { "kind": "record", "values": { "$ref": "#/$defs/V" } } }
]
},
"V": {
"kind": "struct",
"fields": [
{ "name": "tag", "kind": "uint8" },
{ "name": "val", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
// The record's internal walk is the packed walker (no padding):
// count@0..4 + entry0: keylen@4..8=1, key@8='a', tag@9, val@10..14
// = 14 bytes total.
let mut buf = vec![0u8; 14];
buf[0..4].copy_from_slice(&1u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes());
buf[8] = b'a';
buf[9] = 7;
buf[10..14].copy_from_slice(&4242u32.to_le_bytes());
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["meta"]["a"], json!({ "tag": 7, "val": 4242 }));
}
#[test]
fn m4_aligned_record_with_array_values_walks_elements() {
// Record values that are an array of fixed primitives — the
// packed BAST walker's array arm through the aligned record path.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "bins", "kind": { "kind": "record", "values": { "kind": "array", "element": "uint16", "count": 2 } } }
]
}
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
// count@0..4=1, keylen@4..8=1, key@8='a', elems@9..13 (2×u16).
let mut buf = vec![0u8; 13];
buf[0..4].copy_from_slice(&1u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes());
buf[8] = b'a';
buf[9..11].copy_from_slice(&10u16.to_le_bytes());
buf[11..13].copy_from_slice(&20u16.to_le_bytes());
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["bins"], json!({ "a": [10, 20] }));
}
#[test]
fn m4_aligned_record_with_byte_disc_union_values_dispatches() {
// Record values that are a byte-disc union — the packed BAST
// walker's union arm through the aligned record path.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "events", "kind": { "kind": "record", "values": { "$ref": "#/$defs/Ev" } } }
]
},
"Ev": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": { "3": { "$ref": "#/$defs/Read" } }
},
"Read": { "kind": "struct", "fields": [ { "name": "n", "kind": "uint32" } ] }
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
// count@0..4=1, keylen@4..8=1, key@8='a', disc@9=3, n@10..14.
let mut buf = vec![0u8; 14];
buf[0..4].copy_from_slice(&1u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes());
buf[8] = b'a';
buf[9] = 3;
buf[10..14].copy_from_slice(&77u32.to_le_bytes());
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
assert_eq!(v["events"]["a"]["__discriminator"], json!(3));
assert_eq!(v["events"]["a"]["n"], json!(77));
}
#[test]
fn m4_aligned_record_with_field_disc_union_values_dispatches() {
// A field-disc union as record values — the union walker's field
// arm (shared walk + variant flatten) through the aligned record
// path.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "events", "kind": { "kind": "record", "values": { "$ref": "#/$defs/Ev" } } }
]
},
"Ev": {
"kind": "union",
"discriminator": { "kind": "field", "name": "type" },
"fields": [ { "name": "type", "kind": "uint8" }, { "name": "seq", "kind": "uint32" } ],
"mapping": { "1": { "$ref": "#/$defs/Read" } }
},
"Read": { "kind": "struct", "fields": [ { "name": "handle", "kind": "uint32" } ] }
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
// count@0..4=1, keylen@4..8=1, key@8='a', shared: type@9, seq@10..14,
// variant: handle@14..18 → 18 bytes.
let mut buf = vec![0u8; 18];
buf[0..4].copy_from_slice(&1u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes());
buf[8] = b'a';
buf[9] = 1;
buf[10..14].copy_from_slice(&5u32.to_le_bytes());
buf[14..18].copy_from_slice(&77u32.to_le_bytes());
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
let ev = &v["events"]["a"];
assert_eq!(ev["type"], json!(1));
assert_eq!(ev["seq"], json!(5));
assert_eq!(ev["handle"], json!(77));
assert_eq!(ev["__discriminator"], json!("1"));
}
#[test]
fn m4_aligned_record_with_wide_primitive_values_walks_all_arms() {
// The packed BAST walker's remaining primitive arms (i8..bool,
// u64, floats, string, bytes) as record values — 0-execution in
// the review's coverage run.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "vals", "kind": { "kind": "record", "values": { "$ref": "#/$defs/V" } } }
]
},
"V": {
"kind": "struct",
"fields": [
{ "name": "i8", "kind": "int8" },
{ "name": "u64", "kind": "uint64" },
{ "name": "f32", "kind": "float32" },
{ "name": "f64", "kind": "float64" },
{ "name": "b", "kind": "bool" },
{ "name": "s", "kind": "string" },
{ "name": "raw", "kind": "bytes" }
]
}
}
});
let doc = doc_from(&root, "S");
let map = crate::offset_map::OffsetMap::compute(&doc).expect("map");
let mut buf = vec![0u8; 64];
buf[0..4].copy_from_slice(&1u32.to_le_bytes());
buf[4..8].copy_from_slice(&1u32.to_le_bytes());
buf[8] = b'a';
let mut off = 9;
buf[off] = 0xF3; off += 1; // i8 = -13
buf[off..off+8].copy_from_slice(&0x0102030405060708u64.to_le_bytes()); off += 8;
buf[off..off+4].copy_from_slice(&1.5f32.to_le_bytes()); off += 4;
buf[off..off+8].copy_from_slice(&2.5f64.to_le_bytes()); off += 8;
buf[off] = 0x01; off += 1; // bool = true
buf[off..off+4].copy_from_slice(&1u32.to_le_bytes()); off += 4; // string len
buf[off] = b'z'; off += 1;
buf[off..off+4].copy_from_slice(&2u32.to_le_bytes()); off += 4; // bytes len
buf[off] = 0xAA;
buf[off+1] = 0xBB;
let v = materialize_aligned(&doc, &buf, &map).expect("materialize");
let obj = &v["vals"]["a"];
assert_eq!(obj["i8"], json!(-13));
assert_eq!(obj["u64"], json!(0x0102030405060708u64));
assert_eq!(obj["f32"], json!(1.5));
assert_eq!(obj["f64"], json!(2.5));
assert_eq!(obj["s"], json!("z"));
assert_eq!(obj["raw"], json!([0xAA, 0xBB]));
}
#[test]
fn materialize_packed_rejects_non_finite_float() {
let root = json!({
+253
View File
@@ -1464,6 +1464,259 @@ mod tests {
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
// ----- M4 item 2: disc-kind arms, nested-union walk, accessors -------
#[test]
fn m4_field_disc_uint16_dispatches_on_wide_value() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "event", "kind": { "$ref": "#/$defs/Event" } }
]
},
"Event": {
"kind": "union",
"discriminator": { "kind": "field", "name": "type" },
"fields": [ { "name": "type", "kind": "uint16" } ],
"mapping": {
"514": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "x", "kind": "uint8" } ]
}
}
});
let mut buf = vec![0u8; 4];
buf[0..2].copy_from_slice(&514u16.to_le_bytes());
buf[2] = 42;
let mut reader = reader(&root, "S");
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
assert_eq!(name, "event");
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "514");
assert_eq!(variant_start, 2);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader.position(), 3);
}
#[test]
fn m4_field_disc_uint32_dispatches_on_wide_value() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "event", "kind": { "$ref": "#/$defs/Event" } }
]
},
"Event": {
"kind": "union",
"discriminator": { "kind": "field", "name": "type" },
"fields": [ { "name": "type", "kind": "uint32" } ],
"mapping": {
"258": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "x", "kind": "uint8" } ]
}
}
});
let mut buf = vec![0u8; 6];
buf[0..4].copy_from_slice(&258u32.to_le_bytes());
buf[4] = 42;
let mut reader = reader(&root, "S");
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
assert_eq!(name, "event");
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "258");
assert_eq!(variant_start, 4);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader.position(), 5);
}
#[test]
fn m4_field_disc_enum_dispatches_on_index() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "event", "kind": { "$ref": "#/$defs/Event" } }
]
},
"Event": {
"kind": "union",
"discriminator": { "kind": "field", "name": "kind" },
"fields": [ { "name": "kind", "kind": { "$ref": "#/$defs/Kind" } } ],
"mapping": {
"3": { "$ref": "#/$defs/Read" }
}
},
"Kind": { "kind": "enum", "values": ["A", "B", "C", "D"] },
"Read": {
"kind": "struct",
"fields": [ { "name": "x", "kind": "uint8" } ]
}
}
});
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&3u32.to_le_bytes());
buf[4] = 42;
let mut reader = reader(&root, "S");
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
assert_eq!(name, "event");
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "3");
assert_eq!(variant_start, 4);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader.position(), 5);
}
#[test]
fn m4_byte_disc_uint16_and_uint32_arms() {
// The plan reader's byte-disc uint16/uint32 arms (0-execution in
// the review's coverage run — wire-visible dispatch widths).
let u16_root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "packet", "kind": { "$ref": "#/$defs/P16" } }
]
},
"P16": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint16" },
"mapping": { "515": { "$ref": "#/$defs/Read" } }
},
"Read": { "kind": "struct", "fields": [ { "name": "x", "kind": "uint8" } ] }
}
});
let mut buf = vec![0u8; 4];
buf[0..2].copy_from_slice(&515u16.to_le_bytes());
buf[2] = 42;
let mut reader16 = reader(&u16_root, "S");
let (_, value) = reader16.read_next(&buf).unwrap().unwrap();
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "515");
assert_eq!(variant_start, 2);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader16.position(), 3);
let u32_root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "packet", "kind": { "$ref": "#/$defs/P32" } }
]
},
"P32": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint32" },
"mapping": { "258": { "$ref": "#/$defs/Read" } }
},
"Read": { "kind": "struct", "fields": [ { "name": "x", "kind": "uint8" } ] }
}
});
let mut buf = vec![0u8; 6];
buf[0..4].copy_from_slice(&258u32.to_le_bytes());
buf[4] = 42;
let mut reader32 = reader(&u32_root, "S");
let (_, value) = reader32.read_next(&buf).unwrap().unwrap();
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "258");
assert_eq!(variant_start, 4);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader32.position(), 5);
}
#[test]
fn m4_nested_union_variant_size_walk() {
// A byte-disc union whose variant is itself a union — the
// plan_walk_variant_size union arm (0-execution in the review's
// coverage run; the capability phase 1 restored). The inner
// union dispatches on its own discriminator after the outer's.
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "outer", "kind": { "$ref": "#/$defs/Outer" } }
]
},
"Outer": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": { "1": { "$ref": "#/$defs/Inner" } }
},
"Inner": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": { "7": { "$ref": "#/$defs/Leaf" } }
},
"Leaf": { "kind": "struct", "fields": [ { "name": "v", "kind": "uint32" } ] }
}
});
// Wire: outer disc (1) + inner disc (7) + u32 — 6 bytes.
let mut buf = vec![0u8; 6];
buf[0] = 1;
buf[1] = 7;
buf[2..6].copy_from_slice(&42u32.to_le_bytes());
let mut reader = reader(&root, "S");
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
assert_eq!(name, "outer");
match value {
FieldValue::Union { discriminator, variant_start } => {
assert_eq!(discriminator, "1");
assert_eq!(variant_start, 1);
}
other => panic!("expected Union, got {other:?}"),
}
assert_eq!(reader.position(), 6, "nested union's full size walked");
}
#[test]
fn m4_public_schema_and_plan_accessors_return_compiled_doc() {
// The public accessors (phase-2 deliverables) had 0 test calls:
// `schema()` returns the compiled document, `plan()` the compiled
// ReadPlan handle.
let root = json!({
"$defs": {
"S": { "kind": "struct", "fields": [ { "name": "a", "kind": "uint8" } ] }
}
});
let plan = ReadPlan::compile(&root, "S").expect("plan");
let reader = SequentialReader::new(Arc::new(plan));
let schema = reader.schema();
assert_eq!(schema["$defs"]["S"]["fields"][0]["name"], json!("a"));
let p = reader.plan();
assert_eq!(p.fields().len(), 1);
assert_eq!(p.fields()[0].name(), "a");
assert_eq!(p.schema()["$defs"]["S"]["fields"][0]["name"], json!("a"));
}
#[test]
fn rejects_schema_with_missing_root() {
let root = json!({ "$defs": { "Other": { "kind": "struct", "fields": [] } } });