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:
1 parent
5f9793f9c0
commit
2eb086f400
3 files changed
+734
-12
No files matched your search
@@ -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
@@ -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!({
|
||||
|
||||
@@ -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": [] } } });
|
||||
|
||||
Reference in new issue
Block a user