Files
alktype/src/materialize.rs
T
glm-5.3-flash e5f1b9d825 Wire packed read path through ReadPlan (ADR-011 steps 2-4, plan phase 2)
SequentialReader now walks Arc<ReadPlan> instead of re-parsing the
BAST typed tree per field (the 400x read-path gap, review #004 H1);
materialize_packed walks the same plan, unifying the two packed
read-side consumers on one compiled form.

- SequentialReader::new(Arc<ReadPlan>) -> Self, infallible: the
  fallible BastDoc parse moved to ReadPlan::compile (phase 1). The
  reader holds the plan Arc + cursor state only; schema() returns the
  Arc<Value> retained on the plan (review #005 H2 — no
  self-referential struct); new plan() accessor exposes the shared
  plan.
- ReadPlan carries schema: Arc<Value> (set at compile; sub-plans hold
  a Null placeholder — only the root plan is handed out).
- materialize_packed(&ReadPlan, &[u8]): plan-walking packed
  materializer. The aligned path keeps walking BastDoc with the
  retained dummy_field_for/ty_source/materialize_typeref_packed
  helpers (phase 5 Scope Boundary: aligned structure walk is the
  permanent 0.3.0 design).
- Engine: Layout::Packed stores Arc<ReadPlan> alongside the builder;
  sequential_reader() is an Arc::clone (was a full-document Value
  clone); packed validate_bytes calls materialize_packed(&self.plan).
- Stride (deferred decision 4): FieldValue::Array now reports the
  true stride for fixed-size struct/nested-array elements (0.2.0
  returned 0); doc comment documents the behavioral change; no
  existing test asserted the 0, so none needed changing.
- Two parity subtleties found and preserved:
  (a) materialize_plan_composite unwraps the plan's anonymous
      single-field wrapper for primitive array elements/record values
      — without it, materialized records nest each leaf under a
      synthetic object (caught by the record parity test);
  (b) field-disc unions keep 0.2.0's materialized key order
      (__discriminator first), observable under preserve_order.
  Both are now covered by plan-phase tests or construction.

Bench (alktty wire_vs_bast, 1024 chunks/stream): packed read
2.27 us/chunk (review #004) -> 98 ns/chunk p64 / 100 ns/chunk p4k
(~23x; the 400x gap closes to ~17x vs hand-rolled 5.6 ns/chunk).
Residual gap is the per-field String allocation mandated by the
unchanged (String, FieldValue) read_next signature (2 allocs/chunk)
plus data_access bounds checks. sequential_reader() construction:
15.7 ns (was a whole-document clone).

Verification: 465 tests pass unchanged (the existing reader/
materialize/engine suites drive the rewrite through the public API —
only constructor call sites moved to ReadPlan::compile); clippy
-D warnings clean; cargo doc zero warnings; wasm32 release build
green.
2026-09-02 07:52:46 +00:00

1599 lines
58 KiB
Rust

//! Materialize a `serde_json::Value` tree from a binary buffer. Used by
//! [`crate::engine::AlkTypeEngine::validate_bytes`] (ADR-010) to collapse
//! the two-step dance (read bytes -> `Value`, then validate `Value`) into
//! a single call.
//!
//! Packed mode walks the compiled [`ReadPlan`]
//! (ADR-011) — the same compiled form [`crate::sequential_reader`] walks,
//! unifying the two packed read-side consumers on one plan. Aligned mode
//! walks the BAST typed tree, reading leaves at offsets from the
//! [`crate::offset_map::OffsetMap`] (the permanent 0.3.0 design — see the
//! phase 5 Scope Boundary in the 0.3.0 plan). Both produce the same
//! `Value` form; the validator is mode-agnostic.
//!
//! ## `$ref` resolution
//!
//! Packed mode: resolved eagerly at `ReadPlan::compile` — the per-buffer
//! walk never touches the BAST document. Aligned mode: the BAST document
//! ([`crate::bast::BastDoc`]) is threaded through every recursive call so
//! `$ref` pointers (e.g. `"#/$defs/Read"`) in composite variants resolve
//! via [`crate::bast::BastDoc::resolve_typeref`] at walk time —
//! load-bearing for unions and arrays whose variants/elements are `$ref`s
//! into `$defs`.
use crate::bast::{
BastArray, BastDefKind, BastDiscriminator, BastDoc, BastField, BastRecord, BastStruct,
BastType, BastUnion,
};
use crate::data_access;
use crate::error::AlkTypeError;
use crate::read_plan::{
CompositePlan, DiscriminatorPlan, FieldPlan, ReadKind, ReadPlan,
};
use crate::schema::{AlkTypeKind, Endian, VariableEncoding};
use serde_json::{Map, Value};
const U32_SIZE: usize = 4;
const DISCRIMINATOR_KEY: &str = "__discriminator";
/// Materialize a `Value` tree from `buffer` by walking the compiled
/// [`ReadPlan`] in packed mode (sequential, from offset 0).
///
/// The plan's root must be a struct (enforced at `ReadPlan::compile`).
/// Endianness is baked per node at compile time.
pub fn materialize_packed(
plan: &ReadPlan,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let mut offset = 0usize;
let mut obj = Map::new();
for field in plan.fields() {
let value = materialize_plan_field(plan, field, field.name(), buffer, &mut offset)?;
obj.insert(field.name().to_string(), value);
}
Ok(Value::Object(obj))
}
/// Materialize one planned field (packed mode): dispatch on the
/// field's [`ReadKind`], reading leaves via [`crate::data_access`] and
/// recursing into the compiled composite body. `endian` is the field's
/// effective endianness baked into the plan.
fn materialize_plan_field(
plan: &ReadPlan,
field: &FieldPlan,
field_path: &str,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let _ = plan;
match field.kind() {
ReadKind::Primitive(kind) => {
materialize_plan_primitive(*kind, field.endian(), field_path, buffer, offset)
}
ReadKind::Enum => {
let v = data_access::read_enum(buffer, *offset, field_path, field.endian())?;
*offset += 4;
Ok(Value::from(v))
}
ReadKind::Struct => {
let body = match field.body() {
Some(CompositePlan::Struct(p)) => p,
_ => {
return Err(AlkTypeError::Schema(format!(
"internal: struct field {field_path} has no Struct body"
)))
}
};
materialize_plan_struct(body, field_path, buffer, offset)
}
ReadKind::Union => {
let body = field.body().ok_or_else(|| AlkTypeError::Schema(format!(
"internal: union field {field_path} has no body"
)))?;
materialize_plan_union(body, field_path, field.endian(), buffer, offset)
}
ReadKind::Array => {
let body = field.body().ok_or_else(|| AlkTypeError::Schema(format!(
"internal: array field {field_path} has no body"
)))?;
materialize_plan_array(body, field_path, field.endian(), buffer, offset)
}
ReadKind::Record => {
let body = field.body().ok_or_else(|| AlkTypeError::Schema(format!(
"internal: record field {field_path} has no body"
)))?;
materialize_plan_record(body, field_path, field.endian(), buffer, offset)
}
}
}
fn materialize_plan_primitive(
kind: AlkTypeKind,
endian: Endian,
field_path: &str,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
match kind {
AlkTypeKind::Int8 => {
let v = data_access::read_i8(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::from(v))
}
AlkTypeKind::Int16 => {
let v = data_access::read_i16(buffer, *offset, field_path, endian)?;
*offset += 2;
Ok(Value::from(v))
}
AlkTypeKind::Int32 => {
let v = data_access::read_i32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(Value::from(v))
}
AlkTypeKind::Int64 => {
let v = data_access::read_i64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(Value::from(v))
}
AlkTypeKind::Uint8 => {
let v = data_access::read_u8(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::from(v))
}
AlkTypeKind::Uint16 => {
let v = data_access::read_u16(buffer, *offset, field_path, endian)?;
*offset += 2;
Ok(Value::from(v))
}
AlkTypeKind::Uint32 => {
let v = data_access::read_u32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(Value::from(v))
}
AlkTypeKind::Uint64 => {
let v = data_access::read_u64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(Value::from(v))
}
AlkTypeKind::Float32 => {
let v = data_access::read_f32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(number_from_f64(v as f64, field_path)?)
}
AlkTypeKind::Float64 => {
let v = data_access::read_f64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(number_from_f64(v, field_path)?)
}
AlkTypeKind::Boolean => {
let v = data_access::read_bool(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::Bool(v))
}
AlkTypeKind::String => {
let s = data_access::read_string(buffer, *offset, field_path, endian)?;
let len = U32_SIZE
.checked_add(s.len())
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "string size 4 + len overflows usize".to_string(),
})?;
*offset = checked_add_at(*offset, len, field_path, "string")?;
Ok(Value::String(s.to_string()))
}
AlkTypeKind::Bytes => {
let b = data_access::read_bytes(buffer, *offset, field_path, endian)?;
let len = U32_SIZE
.checked_add(b.len())
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "bytes size 4 + len overflows usize".to_string(),
})?;
*offset = checked_add_at(*offset, len, field_path, "bytes")?;
let arr: Vec<Value> = b.iter().map(|&byte| Value::from(u32::from(byte))).collect();
Ok(Value::Array(arr))
}
other => Err(AlkTypeError::Schema(format!(
"materialize: unsupported primitive kind {other} at {field_path}"
))),
}
}
fn checked_add_at(
offset: usize,
size: usize,
field_path: &str,
what: &str,
) -> Result<usize, AlkTypeError> {
offset.checked_add(size).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("{what} offset {offset} + {size} overflows usize"),
})
}
fn materialize_plan_struct(
plan: &ReadPlan,
field_path: &str,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let mut obj = Map::new();
for field in plan.fields() {
let path = format!("{field_path}.{}", field.name());
let value = materialize_plan_field(plan, field, &path, buffer, offset)?;
obj.insert(field.name().to_string(), value);
}
Ok(Value::Object(obj))
}
fn materialize_plan_array(
body: &CompositePlan,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let (element, count) = match body {
CompositePlan::Array {
element,
count,
..
} => (element, *count),
_ => {
return Err(AlkTypeError::Schema(format!(
"internal: array body at {field_path} is not CompositePlan::Array"
)))
}
};
let mut arr = Vec::with_capacity(count);
for i in 0..count {
let path = format!("{field_path}[{i}]");
let value = materialize_plan_composite(element, &path, endian, buffer, offset)?;
arr.push(value);
}
Ok(Value::Array(arr))
}
/// Materialize a composite body used as a TypeRef (array element,
/// record value, union variant).
///
/// Primitive/enum elements and values were wrapped at compile time in an
/// anonymous single-field struct plan (the wrap-leaf convention) so the
/// read loop has a uniform node; the materializer unwraps that wrapper
/// and emits the leaf value directly — the 0.2.0 materializer never
/// wrapped (real schema field names cannot be empty, so an empty name
/// uniquely identifies the wrapper).
fn materialize_plan_composite(
body: &CompositePlan,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
if let CompositePlan::Struct(plan) = body {
if let Some(only) = plan.fields().first() {
if plan.fields().len() == 1
&& only.name().is_empty()
&& matches!(only.kind(), ReadKind::Primitive(_) | ReadKind::Enum)
{
return materialize_plan_field(plan, only, field_path, buffer, offset);
}
}
}
match body {
CompositePlan::Struct(plan) => materialize_plan_struct(plan, field_path, buffer, offset),
CompositePlan::Union { .. } => {
materialize_plan_union(body, field_path, endian, buffer, offset)
}
CompositePlan::Array { .. } => {
materialize_plan_array(body, field_path, endian, buffer, offset)
}
CompositePlan::Record { .. } => {
materialize_plan_record(body, field_path, endian, buffer, offset)
}
}
}
fn materialize_plan_union(
body: &CompositePlan,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let (disc, shared, variants) = match body {
CompositePlan::Union {
disc,
shared,
variants,
} => (disc, shared, variants),
_ => {
return Err(AlkTypeError::Schema(format!(
"internal: union body at {field_path} is not CompositePlan::Union"
)))
}
};
match disc {
DiscriminatorPlan::Byte {
offset: disc_rel,
disc_type,
} => {
let disc_abs = checked_add_at(*offset, *disc_rel, field_path, "discriminator")?;
let disc_value = match disc_type {
AlkTypeKind::Uint8 => {
data_access::read_u8(buffer, disc_abs, field_path)? as u32
}
AlkTypeKind::Uint16 => {
data_access::read_u16(buffer, disc_abs, field_path, endian)? as u32
}
AlkTypeKind::Uint32 => {
data_access::read_u32(buffer, disc_abs, field_path, endian)?
}
other => {
return Err(AlkTypeError::Schema(format!(
"internal: union discriminator type {other} is not a supported byte \
discriminator (parser should have rejected this)"
)));
}
};
let key = disc_value.to_string();
let variant = variants
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v)
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("union discriminator value {key} not in mapping"),
})?;
let disc_size = disc_type.type_size().unwrap_or(1);
let variant_offset =
checked_add_at(disc_abs, disc_size, field_path, "variant offset")?;
*offset = variant_offset;
let variant_value =
materialize_plan_composite(variant, field_path, endian, buffer, offset)?;
Ok(tag_union_value_number(disc_value, variant_value))
}
DiscriminatorPlan::Field { name, field_index } => {
let shared_plan = shared.as_ref().ok_or_else(|| AlkTypeError::Schema(format!(
"internal: field-disc union at {field_path} has no shared plan"
)))?;
let disc_field = shared_plan.fields().get(*field_index).ok_or_else(|| {
AlkTypeError::Schema(format!(
"internal: union {field_path} discriminator index {field_index} out of range"
))
})?;
let _ = disc_field;
// Walk the whole shared plan; capture the discriminator
// field's value for the mapping key as we pass it. The
// result object preserves the 0.2.0 key order:
// `__discriminator` first, then the shared fields, then the
// variant's fields.
let mut disc_value: Option<Value> = None;
let mut shared_values: Vec<(String, Value)> = Vec::new();
for field in shared_plan.fields() {
let path = format!("{field_path}.{}", field.name());
let value = materialize_plan_field(shared_plan, field, &path, buffer, offset)?;
if field.name() == name.as_str() {
disc_value = Some(value);
} else {
shared_values.push((field.name().to_string(), value));
}
}
let disc_value = disc_value.ok_or_else(|| AlkTypeError::Schema(format!(
"internal: union {field_path} discriminator field {name:?} not walked"
)))?;
let key = plan_value_discriminator_key(&disc_value, field_path)?;
let variant = variants
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v)
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("union discriminator value {key} not in mapping"),
})?;
let variant_value =
materialize_plan_composite(variant, field_path, endian, buffer, offset)?;
let mut obj = Map::new();
obj.insert(DISCRIMINATOR_KEY.to_string(), Value::String(key));
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))
}
}
}
/// Stringify a materialized discriminator field value for mapping
/// lookup. Mirrors the 0.2.0 `union_discriminator_key` for the Value form.
fn plan_value_discriminator_key(value: &Value, field_path: &str) -> Result<String, AlkTypeError> {
match value {
Value::String(s) => Ok(s.clone()),
Value::Number(n) => Ok(n.to_string()),
_ => Err(AlkTypeError::Schema(format!(
"union {field_path} has unsupported discriminator value kind: {value}"
))),
}
}
fn materialize_plan_record(
body: &CompositePlan,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let value_body = match body {
CompositePlan::Record { value } => value,
_ => {
return Err(AlkTypeError::Schema(format!(
"internal: record body at {field_path} is not CompositePlan::Record"
)))
}
};
let count = data_access::read_u32(buffer, *offset, field_path, endian)? as usize;
*offset += U32_SIZE;
let mut obj = Map::new();
for i in 0..count {
let key_path = format!("{field_path}[{i}].key");
let key_str = data_access::read_string(buffer, *offset, &key_path, endian)?;
let key_len = U32_SIZE
.checked_add(key_str.len())
.ok_or_else(|| AlkTypeError::Access {
field_path: key_path.clone(),
reason: "key size 4 + len overflows usize".to_string(),
})?;
*offset = checked_add_at(*offset, key_len, &key_path, "key")?;
let val_path = format!("{field_path}[{i}].value");
let val = materialize_plan_composite(value_body, &val_path, endian, buffer, offset)?;
obj.insert(key_str.to_string(), val);
}
Ok(Value::Object(obj))
}
/// Materialize a `Value` tree from `buffer` by walking the BAST root type
/// in aligned mode (offsets from `offset_map`).
///
/// The root type must be a struct. `offset_map` must have been computed
/// from the same BAST document. Endianness is read from the root struct's
/// `endian` annotation (defaults to little-endian).
pub fn materialize_aligned(
doc: &BastDoc<'_>,
buffer: &[u8],
offset_map: &crate::offset_map::OffsetMap,
) -> Result<Value, AlkTypeError> {
let root_def = doc.root_def();
let struct_node = match root_def.kind() {
BastDefKind::Struct(s) => s,
other => {
return Err(AlkTypeError::Schema(format!(
"materialize_aligned: expected a struct at the root, got {kind}",
kind = other.alk_kind()
)));
}
};
let effective_endian = struct_node.endian();
materialize_struct_aligned(doc, struct_node, "", offset_map, effective_endian, buffer)
}
fn materialize_struct_packed(
doc: &BastDoc<'_>,
struct_node: &BastStruct<'_>,
path_prefix: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let mut obj = Map::new();
for field in struct_node.fields() {
let path = if path_prefix.is_empty() {
field.name().to_string()
} else {
format!("{path_prefix}.{}", field.name())
};
let field_endian = field.effective_endian(endian);
let value = materialize_field_packed(doc, field, &path, field_endian, buffer, offset)?;
obj.insert(field.name().to_string(), value);
}
Ok(Value::Object(obj))
}
fn materialize_field_packed(
doc: &BastDoc<'_>,
field: &BastField<'_>,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let field_endian = field.effective_endian(endian);
let ty = field.ty();
let resolved = doc.resolve_typeref(ty)?;
materialize_typeref_packed(doc, &resolved, field, field_path, field_endian, buffer, offset)
}
fn materialize_typeref_packed(
doc: &BastDoc<'_>,
ty: &BastType<'_>,
_field: &BastField<'_>,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
match ty {
BastType::Primitive(AlkTypeKind::Int8) => {
let v = data_access::read_i8(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Int16) => {
let v = data_access::read_i16(buffer, *offset, field_path, endian)?;
*offset += 2;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Int32) => {
let v = data_access::read_i32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Int64) => {
let v = data_access::read_i64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Uint8) => {
let v = data_access::read_u8(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Uint16) => {
let v = data_access::read_u16(buffer, *offset, field_path, endian)?;
*offset += 2;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Uint32) => {
let v = data_access::read_u32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Uint64) => {
let v = data_access::read_u64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::Float32) => {
let v = data_access::read_f32(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(number_from_f64(v as f64, field_path)?)
}
BastType::Primitive(AlkTypeKind::Float64) => {
let v = data_access::read_f64(buffer, *offset, field_path, endian)?;
*offset += 8;
Ok(number_from_f64(v, field_path)?)
}
BastType::Primitive(AlkTypeKind::Boolean) => {
let v = data_access::read_bool(buffer, *offset, field_path)?;
*offset += 1;
Ok(Value::Bool(v))
}
BastType::Enum(_) => {
let v = data_access::read_enum(buffer, *offset, field_path, endian)?;
*offset += 4;
Ok(Value::from(v))
}
BastType::Primitive(AlkTypeKind::String) => {
let s = data_access::read_string(buffer, *offset, field_path, endian)?;
let len = U32_SIZE + s.len();
*offset = offset.checked_add(len).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("string offset {len} overflows usize"),
})?;
Ok(Value::String(s.to_string()))
}
BastType::Primitive(AlkTypeKind::Bytes) => {
let b = data_access::read_bytes(buffer, *offset, field_path, endian)?;
let len = U32_SIZE + b.len();
*offset = offset.checked_add(len).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("bytes offset {len} overflows usize"),
})?;
let arr: Vec<Value> = b.iter().map(|&byte| Value::from(u32::from(byte))).collect();
Ok(Value::Array(arr))
}
BastType::Struct(s) => {
materialize_struct_packed(doc, s, field_path, endian, buffer, offset)
}
BastType::Array(a) => {
materialize_array_packed(doc, a, field_path, endian, buffer, offset)
}
BastType::Union(u) => {
materialize_union_packed(doc, u, field_path, endian, buffer, offset)
}
BastType::Record(r) => {
materialize_record_packed(doc, r, field_path, endian, buffer, offset)
}
BastType::Ref(_) => Err(AlkTypeError::Schema(format!(
"materialize: unresolved $ref at {field_path}"
))),
BastType::Primitive(k) => Err(AlkTypeError::Schema(format!(
"materialize: unsupported primitive kind {k} at {field_path}"
))),
}
}
fn materialize_array_packed(
doc: &BastDoc<'_>,
array: &BastArray<'_>,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let element_ty = array.element();
let count = array.count();
let mut arr = Vec::with_capacity(count);
for i in 0..count {
let path = format!("{field_path}[{i}]");
let resolved_elem = doc.resolve_typeref(element_ty)?;
let value = materialize_typeref_packed(
doc,
&resolved_elem,
&dummy_field_for(&resolved_elem),
&path,
endian,
buffer,
offset,
)?;
arr.push(value);
}
Ok(Value::Array(arr))
}
fn materialize_union_packed(
doc: &BastDoc<'_>,
union_node: &BastUnion<'_>,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let disc = union_node.discriminator();
match disc {
BastDiscriminator::Byte { offset: disc_rel, disc_type } => {
let disc_abs = offset.checked_add(*disc_rel).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("discriminator offset {disc_rel} overflows"),
})?;
let disc_value = match disc_type {
AlkTypeKind::Uint8 => {
data_access::read_u8(buffer, disc_abs, field_path)? as u32
}
AlkTypeKind::Uint16 => {
data_access::read_u16(buffer, disc_abs, field_path, endian)? as u32
}
AlkTypeKind::Uint32 => {
data_access::read_u32(buffer, disc_abs, field_path, endian)?
}
other => {
return Err(AlkTypeError::Schema(format!(
"internal: union discriminator type {other} is not a supported byte \
discriminator (parser should have rejected this)"
)));
}
};
let mapping = union_node.mapping();
let key = disc_value.to_string();
let variant_ty = mapping
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v)
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("union discriminator value {key} not in mapping"),
})?;
let disc_size = disc_type.type_size().unwrap_or(1);
let variant_offset = disc_abs.checked_add(disc_size).ok_or_else(|| {
AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!(
"variant offset {disc_abs} + disc size {disc_size} overflows usize"
),
}
})?;
*offset = variant_offset;
let resolved_variant = doc.resolve_typeref(variant_ty)?;
let variant_value = materialize_typeref_packed(
doc,
&resolved_variant,
&dummy_field_for(&resolved_variant),
field_path,
endian,
buffer,
offset,
)?;
Ok(tag_union_value_number(disc_value, variant_value))
}
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)?;
let mapping = union_node.mapping();
let variant_ty = mapping
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v)
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("union discriminator value {key} not in mapping"),
})?;
let resolved_variant = doc.resolve_typeref(variant_ty)?;
let variant_value = materialize_typeref_packed(
doc,
&resolved_variant,
&dummy_field_for(&resolved_variant),
field_path,
endian,
buffer,
offset,
)?;
let mut obj = Map::new();
obj.insert(DISCRIMINATOR_KEY.to_string(), Value::String(key.clone()));
obj.insert((*name).to_string(), disc_value);
flatten_variant_into(&mut obj, variant_value);
Ok(Value::Object(obj))
}
}
}
fn materialize_record_packed(
doc: &BastDoc<'_>,
record: &BastRecord<'_>,
field_path: &str,
endian: Endian,
buffer: &[u8],
offset: &mut usize,
) -> Result<Value, AlkTypeError> {
let values_ty = record.values();
let count = data_access::read_u32(buffer, *offset, field_path, endian)? as usize;
*offset += U32_SIZE;
let mut obj = Map::new();
for i in 0..count {
let key_path = format!("{field_path}[{i}].key");
let key_str = data_access::read_string(buffer, *offset, &key_path, endian)?;
let key_len = U32_SIZE + key_str.len();
*offset = offset.checked_add(key_len).ok_or_else(|| AlkTypeError::Access {
field_path: key_path.clone(),
reason: "key offset overflows".to_string(),
})?;
let val_path = format!("{field_path}[{i}].value");
let resolved_values = doc.resolve_typeref(values_ty)?;
let val = materialize_typeref_packed(
doc,
&resolved_values,
&dummy_field_for(&resolved_values),
&val_path,
endian,
buffer,
offset,
)?;
obj.insert(key_str.to_string(), val);
}
Ok(Value::Object(obj))
}
fn tag_union_value_number(disc_value: u32, variant_value: Value) -> Value {
let mut obj = Map::new();
obj.insert(DISCRIMINATOR_KEY.to_string(), Value::from(disc_value));
flatten_variant_into(&mut obj, variant_value);
Value::Object(obj)
}
/// Flatten a variant's materialized object fields into `obj` (in place).
/// If the variant is not an object (e.g. a leaf kind), nest it under
/// `"__variant"`.
fn flatten_variant_into(obj: &mut Map<String, Value>, variant_value: Value) {
if let Some(variant_obj) = variant_value.as_object() {
for (k, v) in variant_obj.iter() {
obj.insert(k.clone(), v.clone());
}
} else {
obj.insert("__variant".to_string(), variant_value);
}
}
/// Stringify a discriminator field value for mapping lookup. Mirrors
/// `sequential_reader::discriminator_string_value` for the Value form.
fn union_discriminator_key(value: &Value, field_path: &str) -> Result<String, AlkTypeError> {
match value {
Value::String(s) => Ok(s.clone()),
Value::Number(n) => Ok(n.to_string()),
_ => Err(AlkTypeError::Schema(format!(
"union {field_path} has unsupported discriminator value kind: {value}"
))),
}
}
fn number_from_f64(f: f64, field_path: &str) -> Result<Value, AlkTypeError> {
serde_json::Number::from_f64(f)
.map(Value::Number)
.ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("non-finite float {f} cannot be represented as JSON"),
})
}
/// Aligned-mode materialization: walk the BAST typed tree, but read
/// leaves at offsets from the `OffsetMap` and fall back to packed-style
/// sequential walks for composites (Record) whose internal layout can't
/// be precomputed into the static offset map.
fn materialize_struct_aligned(
doc: &BastDoc<'_>,
struct_node: &BastStruct<'_>,
path_prefix: &str,
offset_map: &crate::offset_map::OffsetMap,
endian: Endian,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let mut obj = Map::new();
for field in struct_node.fields() {
let path = if path_prefix.is_empty() {
field.name().to_string()
} else {
format!("{path_prefix}.{}", field.name())
};
let ty = field.ty();
let resolved = doc.resolve_typeref(ty)?;
let kind = resolved.alk_kind();
let field_endian = field.effective_endian(endian);
let value = if kind.is_fixed_size() {
let range = offset_map.get(&path).ok_or_else(|| AlkTypeError::Offset {
field_path: path.clone(),
reason: "field not found in offset map".to_string(),
})?;
materialize_leaf_at(doc, &resolved, &path, range.start, field_endian, buffer)?
} else if kind == AlkTypeKind::Struct {
if let BastType::Struct(s) = &resolved {
materialize_struct_aligned(doc, s, &path, offset_map, field_endian, buffer)?
} else {
return Err(AlkTypeError::Schema(format!(
"materialize: resolved kind Struct but type was {resolved} at {path}"
)));
}
} else if kind == AlkTypeKind::Array {
if let BastType::Array(a) = &resolved {
materialize_array_aligned(doc, a, &path, offset_map, field_endian, buffer)?
} else {
return Err(AlkTypeError::Schema(format!(
"materialize: resolved kind Array but type was {resolved} at {path}"
)));
}
} else if kind == AlkTypeKind::Record {
let range = offset_map.get(&path).ok_or_else(|| AlkTypeError::Offset {
field_path: path.clone(),
reason: "field not found in offset map".to_string(),
})?;
let mut offset = range.start;
materialize_typeref_packed(
doc,
&resolved,
field,
&path,
field_endian,
buffer,
&mut offset,
)?
} else if kind.is_variable_length() {
let range = offset_map.get(&path).ok_or_else(|| AlkTypeError::Offset {
field_path: path.clone(),
reason: "field not found in offset map".to_string(),
})?;
materialize_variable_aligned(doc, field, &resolved, &path, range, field_endian, buffer)?
} else {
return Err(AlkTypeError::Schema(format!(
"materialize: unsupported kind {kind} at {path} in aligned mode"
)));
};
obj.insert(field.name().to_string(), value);
}
Ok(Value::Object(obj))
}
/// Materialize a fixed-size array in aligned mode by reading each element
/// at its `vals[i]` offset-map entry. Array elements are always fixed-size
/// in aligned mode (the offset map rejects variable-length elements).
fn materialize_array_aligned(
doc: &BastDoc<'_>,
array: &BastArray<'_>,
field_path: &str,
offset_map: &crate::offset_map::OffsetMap,
endian: Endian,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let element_ty = array.element();
let resolved_elem = doc.resolve_typeref(element_ty)?;
let count = array.count();
let mut arr = Vec::with_capacity(count);
for i in 0..count {
let elem_path = format!("{field_path}[{i}]");
let range = offset_map.get(&elem_path).ok_or_else(|| AlkTypeError::Offset {
field_path: elem_path.clone(),
reason: "array element not found in offset map".to_string(),
})?;
let value = materialize_leaf_at(doc, &resolved_elem, &elem_path, range.start, endian, buffer)?;
arr.push(value);
}
Ok(Value::Array(arr))
}
/// Materialize a variable-length leaf (String/Bytes) in aligned
/// mode, dispatching on the field's encoding strategy:
///
/// - `offset-indirect`: an 8-byte `{offset, length}` pair at `start`,
/// pointing absolutely into the same buffer.
/// - `maxLength` reservation: `end - start` bytes of zero-padded data;
/// trailing NUL bytes are trimmed.
/// - inline length-prefixed (default): a 4-byte length prefix at `start`.
fn materialize_variable_aligned(
doc: &BastDoc<'_>,
field: &BastField<'_>,
ty: &BastType<'_>,
field_path: &str,
range: &crate::offset_map::ByteRange,
endian: Endian,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let start = range.start;
match field.encoding() {
VariableEncoding::OffsetIndirect => match ty {
BastType::Primitive(AlkTypeKind::String) => {
let s = data_access::read_string_indirect(buffer, start, field_path, endian)?;
Ok(Value::String(s.to_string()))
}
BastType::Primitive(AlkTypeKind::Bytes) => {
let b = data_access::read_bytes_indirect(buffer, start, field_path, endian)?;
let arr: Vec<Value> = b.iter().map(|&byte| Value::from(u32::from(byte))).collect();
Ok(Value::Array(arr))
}
_ => Err(AlkTypeError::Schema(format!(
"materialize: offset-indirect encoding on non-variable kind at {field_path}"
))),
},
VariableEncoding::LengthPrefixed => {
if let Some(_max_len) = field.max_length() {
let data = buffer.get(start..range.end).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("reservation [{start}..{}) unavailable", range.end),
})?;
let trimmed_len = data
.iter()
.rposition(|&b| b != 0)
.map(|i| i + 1)
.unwrap_or(0);
let trimmed = &data[..trimmed_len];
match ty {
BastType::Primitive(AlkTypeKind::String) => {
let s = std::str::from_utf8(trimmed).map_err(|e| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("invalid UTF-8 in maxLength string: {e}"),
})?;
Ok(Value::String(s.to_string()))
}
BastType::Primitive(AlkTypeKind::Bytes) => {
let arr: Vec<Value> =
trimmed.iter().map(|&byte| Value::from(u32::from(byte))).collect();
Ok(Value::Array(arr))
}
_ => Err(AlkTypeError::Schema(format!(
"materialize: maxLength on non-variable kind at {field_path}"
))),
}
} else {
materialize_leaf_at(doc, ty, field_path, start, endian, buffer)
}
}
}
}
fn materialize_leaf_at(
doc: &BastDoc<'_>,
ty: &BastType<'_>,
field_path: &str,
offset: usize,
endian: Endian,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let mut pos = offset;
materialize_typeref_packed(
doc,
ty,
&dummy_field_for(ty),
field_path,
endian,
buffer,
&mut pos,
)
}
/// Construct a synthetic `BastField` borrowing from a resolved
/// `BastType`'s source node. Used when materializing array elements /
/// record values / union variants — these are TypeRefs, not fields, so
/// they don't carry field-level annotations. The synthetic field
/// inherits no annotations (which is correct: `maxLength`/`encoding`/
/// `endian`/`align` are field-level properties, and array elements and
/// record values don't have them in BAST).
///
/// The `BastField::parse` path validates the node; this helper bypasses
/// re-validation for a node we've already resolved. The returned field
/// borrows from the `BastType`'s source.
fn dummy_field_for<'d>(ty: &BastType<'d>) -> BastField<'d> {
let source = ty_source(ty);
BastField::synthetic(ty.clone(), source)
}
fn ty_source<'d>(ty: &BastType<'d>) -> &'d serde_json::Value {
match ty {
BastType::Struct(s) => s.source(),
BastType::Union(u) => u.source(),
BastType::Array(a) => a.source(),
BastType::Record(r) => r.source(),
_ => &serde_json::Value::Null,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> {
BastDoc::new(root, name).expect("bast doc")
}
fn materialize_packed_strict(
root: &Value,
doc: &BastDoc<'_>,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let plan = ReadPlan::compile(root, doc.root_name())?;
materialize_packed(&plan, buffer)
}
#[test]
fn materialize_bytes_packed_returns_array_of_u8() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [ { "name": "blob", "kind": "bytes" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 7];
buf[0..4].copy_from_slice(&3u32.to_le_bytes());
buf[4] = 0x41;
buf[5] = 0x42;
buf[6] = 0xC3;
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["blob"], json!([65, 66, 195]));
}
#[test]
fn materialize_bytes_packed_non_utf8_round_trips() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [ { "name": "raw", "kind": "bytes" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&4u32.to_le_bytes());
buf[4] = 0xFF;
buf[5] = 0xFE;
buf[6] = 0x00;
buf[7] = 0x80;
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["raw"], json!([255, 254, 0, 128]));
}
#[test]
fn materialize_record_packed_count_prefixed_pairs() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "counts", "kind": { "kind": "record", "values": "uint32" } }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 22];
let mut off = 0usize;
buf[off..off + 4].copy_from_slice(&2u32.to_le_bytes());
off += 4;
buf[off..off + 4].copy_from_slice(&1u32.to_le_bytes());
off += 4;
buf[off] = b'b';
off += 1;
buf[off..off + 4].copy_from_slice(&10u32.to_le_bytes());
off += 4;
buf[off..off + 4].copy_from_slice(&1u32.to_le_bytes());
off += 4;
buf[off] = b'a';
off += 1;
buf[off..off + 4].copy_from_slice(&20u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["counts"], json!({ "b": 10, "a": 20 }));
}
#[test]
fn materialize_record_packed_zero_entries() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "counts", "kind": { "kind": "record", "values": "uint32" } }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 4];
buf[0..4].copy_from_slice(&0u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["counts"], json!({}));
}
#[test]
fn materialize_record_packed_rejects_short_buffer() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "counts", "kind": { "kind": "record", "values": "uint32" } }
]
}
}
});
let doc = doc_from(&root, "S");
let buf = [0u8; 2];
let err = materialize_packed_strict(&root, &doc, &buf).unwrap_err();
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn materialize_union_byte_discriminator_packs_variant_fields() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "payload",
"kind": { "$ref": "#/$defs/Packet" }
}
]
},
"Packet": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": {
"5": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "id", "kind": "uint32" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 5];
buf[0] = 5;
buf[1..5].copy_from_slice(&42u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
let payload = &v["payload"];
assert_eq!(payload["__discriminator"], json!(5));
assert_eq!(payload["id"], json!(42));
}
#[test]
fn materialize_union_field_discriminator_includes_typed_disc_value() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "payload",
"kind": { "$ref": "#/$defs/Event" }
}
]
},
"Event": {
"kind": "union",
"discriminator": { "kind": "field", "name": "type" },
"fields": [ { "name": "type", "kind": "string" } ],
"mapping": {
"read": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "n", "kind": "uint32" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 12];
buf[0..4].copy_from_slice(&4u32.to_le_bytes());
buf[4..8].copy_from_slice(b"read");
buf[8..12].copy_from_slice(&7u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
let payload = &v["payload"];
assert_eq!(payload["__discriminator"], json!("read"));
assert_eq!(payload["type"], json!("read"));
assert_eq!(payload["n"], json!(7));
}
#[test]
fn materialize_union_field_discriminator_advances_offset_correctly() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "payload",
"kind": { "$ref": "#/$defs/Event" }
},
{ "name": "trailer", "kind": "uint32" }
]
},
"Event": {
"kind": "union",
"discriminator": { "kind": "field", "name": "type" },
"fields": [ { "name": "type", "kind": "string" } ],
"mapping": {
"read": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "n", "kind": "uint32" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 16];
buf[0..4].copy_from_slice(&4u32.to_le_bytes());
buf[4..8].copy_from_slice(b"read");
buf[8..12].copy_from_slice(&7u32.to_le_bytes());
buf[12..16].copy_from_slice(&99u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["trailer"], json!(99));
assert_eq!(v["payload"]["n"], json!(7));
}
#[test]
fn materialize_union_byte_discriminator_unknown_value_is_access_error() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "payload",
"kind": { "$ref": "#/$defs/Packet" }
}
]
},
"Packet": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": {
"5": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": []
}
}
});
let doc = doc_from(&root, "S");
let buf = [99u8];
let err = materialize_packed_strict(&root, &doc, &buf).unwrap_err();
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn materialize_union_with_ref_variant_resolves_via_root() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "payload",
"kind": { "$ref": "#/$defs/Packet" }
}
]
},
"Packet": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": {
"5": { "$ref": "#/$defs/Read" }
}
},
"Read": {
"kind": "struct",
"fields": [ { "name": "id", "kind": "uint32" } ]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 5];
buf[0] = 5;
buf[1..5].copy_from_slice(&42u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["payload"]["__discriminator"], json!(5));
assert_eq!(v["payload"]["id"], json!(42));
}
#[test]
fn materialize_array_with_ref_element_resolves_via_root() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "items",
"kind": {
"kind": "array",
"element": { "$ref": "#/$defs/Point" },
"count": 2
}
}
]
},
"Point": {
"kind": "struct",
"fields": [
{ "name": "x", "kind": "uint16" },
{ "name": "y", "kind": "uint16" }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8];
buf[0..2].copy_from_slice(&1u16.to_le_bytes());
buf[2..4].copy_from_slice(&2u16.to_le_bytes());
buf[4..6].copy_from_slice(&3u16.to_le_bytes());
buf[6..8].copy_from_slice(&4u16.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["items"], json!([{ "x": 1, "y": 2 }, { "x": 3, "y": 4 }]));
}
#[test]
fn materialize_struct_with_nested_struct_then_field() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{
"name": "header",
"kind": {
"kind": "struct",
"fields": [
{ "name": "a", "kind": "uint8" },
{ "name": "b", "kind": "uint8" }
]
}
},
{ "name": "after", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 6];
buf[0] = 1;
buf[1] = 2;
buf[2..6].copy_from_slice(&0x03030303u32.to_le_bytes());
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["header"]["a"], json!(1));
assert_eq!(v["header"]["b"], json!(2));
assert_eq!(v["after"], json!(0x03030303u32));
}
#[test]
fn materialize_chunk_header_packed() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"endian": "big",
"fields": [
{ "name": "channel_id", "kind": "uint32" },
{ "name": "length", "kind": "uint32" }
]
}
}
});
let doc = doc_from(&root, "S");
let buf = [0u8, 0u8, 0u8, 42u8, 0u8, 0u8, 0u8, 7u8];
let v = materialize_packed_strict(&root, &doc, &buf).expect("materialize");
assert_eq!(v["channel_id"], json!(42));
assert_eq!(v["length"], json!(7));
}
// ----- Aligned-mode materialization (M2 fixes) ----------------------
fn materialize_aligned_strict(
doc: &BastDoc<'_>,
buffer: &[u8],
) -> Result<Value, AlkTypeError> {
let offset_map = crate::offset_map::OffsetMap::compute(doc)?;
materialize_aligned(doc, buffer, &offset_map)
}
#[test]
fn materialize_aligned_fixed_size_array() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "vals", "kind": { "kind": "array", "element": "uint32", "count": 2 } }
]
}
}
});
let doc = doc_from(&root, "S");
let buf = [1u8, 0, 0, 0, 2, 0, 0, 0];
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["vals"], json!([1, 2]));
}
#[test]
fn materialize_aligned_max_length_string_trims_padding() {
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[..5].copy_from_slice(b"hello");
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["name"], json!("hello"));
}
#[test]
fn materialize_aligned_max_length_bytes_trims_padding() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "blob", "kind": "bytes", "maxLength": 8 }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8];
buf[..3].copy_from_slice(&[0xAA, 0xBB, 0xCC]);
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["blob"], json!([0xAA, 0xBB, 0xCC]));
}
#[test]
fn materialize_aligned_offset_indirect_bytes() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "blob", "kind": "bytes", "encoding": "offset-indirect" }
]
}
}
});
let doc = doc_from(&root, "S");
let mut buf = vec![0u8; 8 + 3];
buf[0..4].copy_from_slice(&8u32.to_le_bytes());
buf[4..8].copy_from_slice(&3u32.to_le_bytes());
buf[8..11].copy_from_slice(&[0xAA, 0xBB, 0xCC]);
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["blob"], json!([0xAA, 0xBB, 0xCC]));
}
#[test]
fn materialize_aligned_offset_indirect_string() {
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 + 5];
buf[0..4].copy_from_slice(&8u32.to_le_bytes());
buf[4..8].copy_from_slice(&5u32.to_le_bytes());
buf[8..13].copy_from_slice(b"hello");
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["name"], json!("hello"));
}
#[test]
fn materialize_aligned_honors_field_level_endian_override() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"endian": "big",
"fields": [
{ "name": "crc", "kind": "uint32", "endian": "little" }
]
}
}
});
let doc = doc_from(&root, "S");
let buf = [0x04u8, 0x03, 0x02, 0x01];
let v = materialize_aligned_strict(&doc, &buf).expect("materialize");
assert_eq!(v["crc"], json!(0x01020304u32));
}
#[test]
fn materialize_packed_rejects_non_finite_float() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [ { "name": "f", "kind": "float32" } ]
}
}
});
let doc = doc_from(&root, "S");
let buf = f32::NAN.to_le_bytes();
let err = materialize_packed_strict(&root, &doc, &buf).unwrap_err();
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
}