//! POC round-trip tests adapted from the `@alkdev/alknet: alknet-typedef-poc/` //! workspace. //! //! These tests re-validate the byte-identical round-trip behaviour that //! the POC verified: fixed-size primitives, length-prefixed strings and //! bytes, nested structs, big-endian, alignment padding, packed-layout //! `LayoutBuilder` with `data_access` writes, and `SequentialReader` //! walks. Each test writes values to a buffer at computed offsets and //! reads them back, asserting both the values and (where applicable) //! the byte positions. //! //! All schemas are BAST documents (`{ "$defs": { ... } }` with `kind`- //! based vocabulary). The root type name is passed to `OffsetMap::compute` //! / `LayoutBuilder::new` / `SequentialReader::new` / `AlkTypeEngine::compile`. use alktype::bast::BastDoc; use alktype::data_access; use alktype::tunion; use alktype::*; use serde_json::json; use std::collections::HashMap; fn var_sizes(pairs: &[(&str, usize)]) -> HashMap { pairs.iter().map(|(k, v)| (k.to_string(), *v)).collect() } #[test] fn fixed_size_round_trip_via_offset_map() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ { "name": "id", "kind": "uint32" }, { "name": "score", "kind": "float32" }, { "name": "flag", "kind": "uint8" }, { "name": "count", "kind": "uint16" } ] } } }); let doc = BastDoc::new(&root, "S")?; let offset_map = OffsetMap::compute(&doc)?; let mut buffer = vec![0u8; offset_map.total_size()]; let id_range = offset_map.get("id").expect("id range"); data_access::write_u32(&mut buffer, id_range.start(), 42, "id", Endian::Little)?; let score_range = offset_map.get("score").expect("score range"); data_access::write_f32(&mut buffer, score_range.start(), 1.5, "score", Endian::Little)?; let flag_range = offset_map.get("flag").expect("flag range"); data_access::write_u8(&mut buffer, flag_range.start(), 1, "flag")?; let count_range = offset_map.get("count").expect("count range"); data_access::write_u16( &mut buffer, count_range.start(), 1000, "count", Endian::Little, )?; assert_eq!( data_access::read_u32(&buffer, id_range.start(), "id", Endian::Little)?, 42 ); let score = data_access::read_f32(&buffer, score_range.start(), "score", Endian::Little)?; assert!((score - 1.5).abs() < 0.001, "score: {score}"); assert_eq!(data_access::read_u8(&buffer, flag_range.start(), "flag")?, 1); assert_eq!( data_access::read_u16(&buffer, count_range.start(), "count", Endian::Little)?, 1000 ); Ok(()) } #[test] fn fixed_size_round_trip_via_engine_aligned() -> Result<(), AlkTypeError> { let doc = json!({ "$defs": { "S": { "kind": "struct", "endian": "little", "fields": [ { "name": "id", "kind": "uint32" }, { "name": "score", "kind": "float32" }, { "name": "flag", "kind": "uint8" } ] } } }); let engine = AlkTypeEngine::compile(&doc, "S", LayoutMode::Aligned, None)?; let offset_map = engine.offset_map().expect("aligned mode has offset_map"); let mut buffer = vec![0u8; offset_map.total_size()]; engine.write_field(&mut buffer, "id", &FieldValue::U32(42))?; engine.write_field(&mut buffer, "score", &FieldValue::F32(1.5))?; engine.write_field(&mut buffer, "flag", &FieldValue::U8(1))?; assert_eq!(engine.read_field(&buffer, "id")?, FieldValue::U32(42)); let score = match engine.read_field(&buffer, "score")? { FieldValue::F32(f) => f, other => panic!("expected F32, got {other:?}"), }; assert!((score - 1.5).abs() < 0.001); assert_eq!(engine.read_field(&buffer, "flag")?, FieldValue::U8(1)); Ok(()) } #[test] fn string_round_trip_via_data_access() -> Result<(), AlkTypeError> { let mut buffer = vec![0u8; 32]; let written = data_access::write_string(&mut buffer, 0, "hello", "name", Endian::Little)?; assert_eq!(written, 4 + 5); assert_eq!(buffer[0..4], 5u32.to_le_bytes()); assert_eq!(&buffer[4..9], b"hello"); assert_eq!( data_access::read_string(&buffer, 0, "name", Endian::Little)?, "hello" ); Ok(()) } #[test] fn string_round_trip_via_engine_aligned() -> Result<(), AlkTypeError> { let doc = json!({ "$defs": { "S": { "kind": "struct", "fields": [ { "name": "name", "kind": "string" } ] } } }); let engine = AlkTypeEngine::compile(&doc, "S", LayoutMode::Aligned, None)?; let offset_map = engine.offset_map().expect("aligned mode has offset_map"); let mut buffer = vec![0u8; offset_map.total_size() + 64]; engine.write_field(&mut buffer, "name", &FieldValue::String("hello"))?; assert_eq!( engine.read_field(&buffer, "name")?, FieldValue::String("hello") ); Ok(()) } #[test] fn bytes_round_trip_via_data_access() -> Result<(), AlkTypeError> { let payload = [0xAA, 0xBB, 0xCC, 0xDD]; let mut buffer = vec![0u8; 32]; let written = data_access::write_bytes(&mut buffer, 0, &payload, "data", Endian::Little)?; assert_eq!(written, 4 + 4); assert_eq!(buffer[0..4], 4u32.to_le_bytes()); assert_eq!(&buffer[4..8], &payload); assert_eq!( data_access::read_bytes(&buffer, 0, "data", Endian::Little)?, &payload[..] ); Ok(()) } #[test] fn nested_struct_round_trip_via_offset_map() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ { "name": "header", "kind": { "kind": "struct", "fields": [ { "name": "version", "kind": "uint32" }, { "name": "magic", "kind": "uint32" } ] } }, { "name": "payload", "kind": "bytes" } ] } } }); let doc = BastDoc::new(&root, "S")?; let offset_map = OffsetMap::compute(&doc)?; let header_version = offset_map.get("header.version").expect("header.version"); let header_magic = offset_map.get("header.magic").expect("header.magic"); let payload_prefix = offset_map.get("payload").expect("payload"); assert_eq!(header_version.start(), 0); assert_eq!(header_magic.start(), 4); assert_eq!(payload_prefix.start(), 8); let data = b"body-data".to_vec(); let mut buffer = vec![0u8; offset_map.total_size() + data.len()]; data_access::write_u32( &mut buffer, header_version.start(), 1, "header.version", Endian::Little, )?; data_access::write_u32( &mut buffer, header_magic.start(), 0xCAFEBABE, "header.magic", Endian::Little, )?; data_access::write_bytes( &mut buffer, payload_prefix.start(), &data, "payload", Endian::Little, )?; assert_eq!( data_access::read_u32( &buffer, header_version.start(), "header.version", Endian::Little )?, 1 ); assert_eq!( data_access::read_u32(&buffer, header_magic.start(), "header.magic", Endian::Little)?, 0xCAFEBABE ); assert_eq!( data_access::read_bytes(&buffer, payload_prefix.start(), "payload", Endian::Little)?, &data[..] ); Ok(()) } #[test] fn nested_struct_round_trip_via_engine_aligned() -> Result<(), AlkTypeError> { let doc = json!({ "$defs": { "S": { "kind": "struct", "fields": [ { "name": "header", "kind": { "kind": "struct", "fields": [ { "name": "version", "kind": "uint8" }, { "name": "flags", "kind": "uint8" } ] } }, { "name": "payload_len", "kind": "uint32" } ] } } }); let engine = AlkTypeEngine::compile(&doc, "S", LayoutMode::Aligned, None)?; let offset_map = engine.offset_map().expect("aligned mode"); assert_eq!(offset_map.get("header.version").unwrap().start(), 0); assert_eq!(offset_map.get("header.flags").unwrap().start(), 1); assert_eq!(offset_map.get("payload_len").unwrap().start(), 4); let mut buffer = vec![0u8; offset_map.total_size()]; engine.write_field(&mut buffer, "header.version", &FieldValue::U8(1))?; engine.write_field(&mut buffer, "header.flags", &FieldValue::U8(0x0F))?; engine.write_field(&mut buffer, "payload_len", &FieldValue::U32(1024))?; assert_eq!( engine.read_field(&buffer, "header.version")?, FieldValue::U8(1) ); assert_eq!( engine.read_field(&buffer, "header.flags")?, FieldValue::U8(0x0F) ); assert_eq!( engine.read_field(&buffer, "payload_len")?, FieldValue::U32(1024) ); Ok(()) } #[test] fn big_endian_round_trip_via_offset_map() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "big", "fields": [ { "name": "id", "kind": "uint32" }, { "name": "offset", "kind": "float64" } ] } } }); let doc = BastDoc::new(&root, "S")?; let offset_map = OffsetMap::compute(&doc)?; let endian = Endian::Big; let id_range = offset_map.get("id").expect("id"); let offset_range = offset_map.get("offset").expect("offset"); assert_eq!(id_range.start(), 0); assert_eq!(offset_range.start(), 8); let value: f64 = std::f64::consts::PI; let mut buffer = vec![0u8; offset_map.total_size()]; data_access::write_u32(&mut buffer, id_range.start(), 0x01020304, "id", endian)?; data_access::write_f64(&mut buffer, offset_range.start(), value, "offset", endian)?; assert_eq!(&buffer[0..4], &[0x01, 0x02, 0x03, 0x04]); assert_eq!(&buffer[4..8], &[0x00, 0x00, 0x00, 0x00]); assert_eq!(&buffer[8..16], value.to_be_bytes()); assert_eq!( data_access::read_u32(&buffer, id_range.start(), "id", endian)?, 0x01020304 ); let read = data_access::read_f64(&buffer, offset_range.start(), "offset", endian)?; assert!((read - value).abs() < 1e-12); Ok(()) } #[test] fn alignment_padding_round_trip_u8_then_u32() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ { "name": "flag", "kind": "uint8" }, { "name": "id", "kind": "uint32" } ] } } }); let doc = BastDoc::new(&root, "S")?; let offset_map = OffsetMap::compute(&doc)?; let flag_range = offset_map.get("flag").expect("flag"); let id_range = offset_map.get("id").expect("id"); assert_eq!(flag_range.start(), 0); assert_eq!(flag_range.end(), 1); assert_eq!(id_range.start(), 4); assert_eq!(id_range.end(), 8); assert_eq!(offset_map.total_size(), 8); let mut buffer = vec![0u8; offset_map.total_size()]; data_access::write_u8(&mut buffer, flag_range.start(), 0xAB, "flag")?; data_access::write_u32( &mut buffer, id_range.start(), 0x01020304, "id", Endian::Little, )?; assert_eq!(buffer[0], 0xAB); assert_eq!(&buffer[1..4], &[0x00, 0x00, 0x00]); assert_eq!(&buffer[4..8], 0x01020304u32.to_le_bytes()); assert_eq!( data_access::read_u8(&buffer, flag_range.start(), "flag")?, 0xAB ); assert_eq!( data_access::read_u32(&buffer, id_range.start(), "id", Endian::Little)?, 0x01020304 ); Ok(()) } #[test] fn packed_layout_round_trip_via_layout_builder() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "little", "fields": [ { "name": "flag", "kind": "uint8" }, { "name": "id", "kind": "uint32" }, { "name": "payload", "kind": "string" } ] } } }); let builder = LayoutBuilder::new(&root, "S")?; let layout = builder.build(&var_sizes(&[("payload", 10)]))?; let flag_pos = layout.get("flag").expect("flag"); let id_pos = layout.get("id").expect("id"); let payload_pos = layout.get("payload").expect("payload"); assert_eq!(flag_pos.offset, 0); assert_eq!(id_pos.offset, 1); assert_eq!(payload_pos.offset, 5); assert_eq!(layout.total_size(), 19); let payload_str = "ten bytes!"; let payload_bytes = payload_str.as_bytes(); assert_eq!(payload_bytes.len(), 10); let mut buffer = vec![0u8; layout.total_size()]; data_access::write_u8(&mut buffer, flag_pos.offset, 0xAB, "flag")?; data_access::write_u32(&mut buffer, id_pos.offset, 0x01020304, "id", Endian::Little)?; data_access::write_string( &mut buffer, payload_pos.offset, payload_str, "payload", Endian::Little, )?; assert_eq!(buffer[0], 0xAB); assert_eq!(&buffer[1..5], 0x01020304u32.to_le_bytes()); assert_eq!(&buffer[5..9], 10u32.to_le_bytes()); assert_eq!(&buffer[9..19], payload_bytes); assert_eq!( data_access::read_u8(&buffer, flag_pos.offset, "flag")?, 0xAB ); assert_eq!( data_access::read_u32(&buffer, id_pos.offset, "id", Endian::Little)?, 0x01020304 ); assert_eq!( data_access::read_string(&buffer, payload_pos.offset, "payload", Endian::Little)?, payload_str ); Ok(()) } #[test] fn sequential_reader_round_trip_packed_buffer() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "little", "fields": [ { "name": "id", "kind": "uint8" }, { "name": "name", "kind": "string" }, { "name": "tail", "kind": "uint8" } ] } } }); let builder = LayoutBuilder::new(&root, "S")?; let payload = "hello"; let layout = builder.build(&var_sizes(&[("name", payload.len())]))?; let mut buffer = vec![0u8; layout.total_size()]; data_access::write_u8(&mut buffer, 0, 7, "id")?; data_access::write_string(&mut buffer, 1, payload, "name", Endian::Little)?; let after = 1 + 4 + payload.len(); data_access::write_u8(&mut buffer, after, 99, "tail")?; let plan = ReadPlan::compile(&root, "S")?; let mut reader = SequentialReader::new(std::sync::Arc::new(plan)); assert_eq!(reader.endian(), Endian::Little); assert_eq!(reader.position(), 0); let (name, value) = reader.read_next(&buffer)?.expect("field 0"); assert_eq!(name, "id"); assert_eq!(value, FieldValue::U8(7)); assert_eq!(reader.position(), 1); let (name, value) = reader.read_next(&buffer)?.expect("field 1"); assert_eq!(name, "name"); assert_eq!(value, FieldValue::String("hello")); assert_eq!(reader.position(), after); let (name, value) = reader.read_next(&buffer)?.expect("field 2"); assert_eq!(name, "tail"); assert_eq!(value, FieldValue::U8(99)); assert_eq!(reader.position(), after + 1); assert!(reader.read_next(&buffer)?.is_none()); Ok(()) } #[test] fn sequential_reader_read_field_walks_preceding_fields() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "little", "fields": [ { "name": "a", "kind": "uint8" }, { "name": "b", "kind": "uint32" }, { "name": "c", "kind": "uint8" } ] } } }); let mut buffer = vec![0u8; 16]; data_access::write_u8(&mut buffer, 0, 1, "a")?; data_access::write_u32(&mut buffer, 1, 0xDEADBEEF, "b", Endian::Little)?; data_access::write_u8(&mut buffer, 5, 9, "c")?; let plan = ReadPlan::compile(&root, "S")?; let mut reader = SequentialReader::new(std::sync::Arc::new(plan)); let value = reader.read_field(&buffer, "c")?; assert_eq!(value, FieldValue::U8(9)); assert_eq!(reader.position(), 6); reader.reset(); let value = reader.read_field(&buffer, "b")?; assert_eq!(value, FieldValue::U32(0xDEADBEEF)); Ok(()) } #[test] fn tunion_byte_offset_discriminator_dispatch() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "Packet": { "kind": "union", "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, "mapping": { "5": { "$ref": "#/$defs/Read" }, "6": { "$ref": "#/$defs/Write" } } }, "Read": { "kind": "struct", "fields": [ { "name": "handle", "kind": "uint32" }, { "name": "length", "kind": "uint32" } ] }, "Write": { "kind": "struct", "fields": [ { "name": "handle", "kind": "uint32" }, { "name": "length", "kind": "uint32" }, { "name": "data", "kind": "uint32" } ] } } }); let doc = BastDoc::new(&root, "Packet")?; let union_def = match doc.root_def().kind() { alktype::bast::BastDefKind::Union(u) => u, _ => unreachable!(), }; let mut buffer = vec![0u8; 32]; buffer[0] = 5; data_access::write_u32(&mut buffer, 1, 0x01020304, "Read.handle", Endian::Big)?; data_access::write_u32(&mut buffer, 5, 4096, "Read.length", Endian::Big)?; let dispatch = tunion::read_byte_discriminator(&buffer, union_def, Endian::Big)?; assert_eq!(dispatch.key, "5"); assert_eq!(dispatch.variant_offset, 1); assert_eq!(dispatch.discriminator_size, 1); let variant = tunion::resolve_variant(union_def, &dispatch.key)?; match variant { alktype::bast::BastType::Ref(r) => assert_eq!(r.name(), "Read"), other => panic!("expected Ref to Read, got {other:?}"), } Ok(()) } #[test] fn tunion_byte_offset_discriminator_size_lookup() -> Result<(), AlkTypeError> { fn union_with(disc_type: &str) -> serde_json::Value { json!({ "$defs": { "U": { "kind": "union", "discriminator": { "kind": "byte", "type": disc_type }, "mapping": { "1": { "$ref": "#/$defs/A" } } }, "A": { "kind": "struct", "fields": [] } } }) } let u8_root = union_with("uint8"); let u16_root = union_with("uint16"); let u32_root = union_with("uint32"); let u8_doc = BastDoc::new(&u8_root, "U")?; let u16_doc = BastDoc::new(&u16_root, "U")?; let u32_doc = BastDoc::new(&u32_root, "U")?; let u8_union = match u8_doc.root_def().kind() { alktype::bast::BastDefKind::Union(u) => u, _ => unreachable!(), }; let u16_union = match u16_doc.root_def().kind() { alktype::bast::BastDefKind::Union(u) => u, _ => unreachable!(), }; let u32_union = match u32_doc.root_def().kind() { alktype::bast::BastDefKind::Union(u) => u, _ => unreachable!(), }; assert_eq!(tunion::discriminator_size(u8_union)?, 1); assert_eq!(tunion::discriminator_size(u16_union)?, 2); assert_eq!(tunion::discriminator_size(u32_union)?, 4); Ok(()) } // --------------------------------------------------------------------------- // L6 (review #006): the single roundtrip test through a field-disc union. // Write with LayoutBuilder → read with SequentialReader → materialize → // validate_bytes on the engine — one schema, all three packed-mode // consumers, so the H3 wire-convention split can never reappear silently. // // Fixture shape: the union's `fields` carry the discriminator (`type`, // uint8) *and* a second shared field (`seq`, uint32); the variant // (`Read`) declares only its own field (`handle`) — it does NOT // re-declare the discriminator or any shared field (forbidden since the // ADR-011 addendum). The disc field is a uint8, so the mapping keys are // stringified integers ("1" = read). The builder lays out // shared-then-variant: // type@0 (1B), seq@1 (4B), handle@5 (4B) — total 9. // --------------------------------------------------------------------------- #[test] fn field_disc_union_roundtrip_build_read_materialize_validate() -> Result<(), AlkTypeError> { let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "little", "fields": [ { "name": "event", "kind": { "$ref": "#/$defs/Event" } }, { "name": "trailer", "kind": "uint8" } ] }, "Event": { "kind": "union", "discriminator": { "kind": "field", "name": "type" }, "fields": [ { "name": "type", "kind": "uint8" }, { "name": "seq", "kind": "uint32" } ], "mapping": { "1": { "$ref": "#/$defs/Read" }, "2": { "$ref": "#/$defs/Write" } } }, "Read": { "kind": "struct", "fields": [ { "name": "handle", "kind": "uint32" } ] }, "Write": { "kind": "struct", "fields": [ { "name": "handle", "kind": "uint32" } ] } } }); // --- Write side: LayoutBuilder (shared-then-variant layout) -------- let builder = LayoutBuilder::new(&root, "S")?; let layout = builder.build(&var_sizes(&[("event.__variant", 0)]))?; assert_eq!(layout.total_size(), 9 + 1, "shared(1+4) + variant(4) + trailer(1)"); let handle_pos = layout.get("event.handle").expect("event.handle"); assert_eq!(handle_pos.offset, 5, "variant fields start after shared (type@0, seq@1)"); let disc_pos = layout.get("event.type").expect("event.type"); assert_eq!(disc_pos.offset, 0); let seq_pos = layout.get("event.seq").expect("event.seq"); assert_eq!(seq_pos.offset, 1); let mut buffer = vec![0u8; layout.total_size()]; data_access::write_u8(&mut buffer, 0, 1, "event.type")?; // mapping key "1" data_access::write_u32(&mut buffer, 1, 77, "event.seq", Endian::Little)?; data_access::write_u32(&mut buffer, 5, 4242, "event.handle", Endian::Little)?; data_access::write_u8(&mut buffer, 9, 55, "trailer")?; // --- Engine (packed) + reader + materializer + validator ----------- let engine = AlkTypeEngine::compile(&root, "S", LayoutMode::Packed, None)?; // validate_bytes (materializer + ValidationPlan) accepts the buffer. engine.validate_bytes(&buffer)?; // SequentialReader: the union field reports the disc value and the // variant start (after the shared walk). let mut reader = engine.sequential_reader().expect("packed mode has reader"); let (name, value) = reader.read_next(&buffer)?.expect("event"); assert_eq!(name, "event"); let (disc, variant_start) = match &value { FieldValue::Union { discriminator, variant_start } => (discriminator.clone(), *variant_start), other => panic!("expected Union, got {other:?}"), }; assert_eq!(disc, "1", "uint8 disc value 1 stringifies to the mapping key"); assert_eq!(variant_start, 5, "variant starts after the shared walk"); let (name, value) = reader.read_next(&buffer)?.expect("trailer"); assert_eq!(name, "trailer"); assert_eq!(value, FieldValue::U8(55)); // materialize_packed through the engine's plan: shared fields land // in the object, the variant's handle flattens in. let plan = engine_sequential_plan(&root, "S")?; let value = alktype::materialize::materialize_packed(&plan, &buffer)?; assert_eq!(value["event"]["type"], json!(1)); assert_eq!(value["event"]["seq"], json!(77)); assert_eq!(value["event"]["handle"], json!(4242)); assert_eq!(value["event"]["__discriminator"], json!("1")); assert_eq!(value["trailer"], json!(55)); Ok(()) } fn engine_sequential_plan(root: &serde_json::Value, name: &str) -> Result, AlkTypeError> { Ok(std::sync::Arc::new(ReadPlan::compile(root, name)?)) }