Port alknet-typedef crate from alknet

Copy the binary struct engine (src/, tests/) verbatim from
alknet/crates/alknet-typedef and create a standalone Cargo.toml
(workspace-inherited fields inlined). Port the architecture docs
(specs, ADRs 095-102, OQs 069-071) from alknet's nested multi-crate
layout to a flat single-crate layout, fixing relative link paths.

Build, 295 tests, and clippy all pass clean.
This commit is contained in:
glm-5.2 committed 2026-08-02 05:59:12 +00:00
1 parent eac7ad88b3
commit 2c4a4994dc
36 files changed
+13805

No files matched your search

+389
View File
@@ -0,0 +1,389 @@
//! Integration tests for the `TypedefEngine` public API.
//!
//! Exercises the engine across both layout modes, the convenience
//! accessors, validation convenience methods, and the aligned-mode
//! `read_field` / `write_field` round-trip for the fixed-size primitive
//! kinds and length-prefixed `String` / `Bytes`.
use alknet_typedef::*;
use serde_json::json;
fn mixed_fixed_struct_schema() -> serde_json::Value {
json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true },
"score": { "TypeDef:Float32": true },
"tag": { "TypeDef:String": true }
}
})
}
#[test]
fn compile_aligned_builds_engine_with_offset_map() -> Result<(), TypedefError> {
let mut schema = mixed_fixed_struct_schema();
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
assert_eq!(engine.mode(), LayoutMode::Aligned);
assert!(engine.offset_map().is_some());
assert!(engine.layout_builder().is_none());
assert!(engine.sequential_reader().is_none());
Ok(())
}
#[test]
fn compile_packed_builds_engine_with_builder_and_reader() -> Result<(), TypedefError> {
let mut schema = mixed_fixed_struct_schema();
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
assert_eq!(engine.mode(), LayoutMode::Packed);
assert!(engine.offset_map().is_none());
assert!(engine.layout_builder().is_some());
assert!(engine.sequential_reader().is_some());
Ok(())
}
#[test]
fn compile_normalizes_bare_name_refs() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"child": { "$ref": "Child" }
},
"$defs": {
"Child": {
"TypeDef:Struct": true,
"properties": { "x": { "TypeDef:Uint8": true } }
}
}
});
let _engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
assert_eq!(
schema["properties"]["child"]["$ref"],
json!("#/$defs/Child")
);
Ok(())
}
#[test]
fn compile_leaves_full_pointer_refs_unchanged() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"child": { "$ref": "#/$defs/Child" }
},
"$defs": {
"Child": {
"TypeDef:Struct": true,
"properties": { "x": { "TypeDef:Uint8": true } }
}
}
});
let _engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
assert_eq!(
schema["properties"]["child"]["$ref"],
json!("#/$defs/Child")
);
Ok(())
}
#[test]
fn compile_returns_schema_error_when_no_typedef_kind() {
let mut schema = json!({ "type": "object", "properties": {} });
let err = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn endian_parsed_from_schema_big() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"endian": "big",
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
assert_eq!(engine.endian(), Endian::Big);
Ok(())
}
#[test]
fn endian_defaults_to_little() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
assert_eq!(engine.endian(), Endian::Little);
Ok(())
}
#[test]
fn validate_json_accepts_valid_instance() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
assert!(engine.validate_json(&json!({"id": 42})).is_ok());
Ok(())
}
#[test]
fn validate_json_rejects_invalid_instance() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let err = engine.validate_json(&json!({"id": -1})).unwrap_err();
assert!(matches!(err, TypedefError::Validation(_)), "got {err:?}");
Ok(())
}
#[test]
fn is_valid_json_returns_bool() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
assert!(engine.is_valid_json(&json!({"id": 42})));
assert!(!engine.is_valid_json(&json!({"id": -1})));
Ok(())
}
#[test]
fn read_write_aligned_round_trips_all_fixed_size_kinds() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"i8": { "TypeDef:Int8": true },
"u8": { "TypeDef:Uint8": true },
"i16": { "TypeDef:Int16": true },
"u16": { "TypeDef:Uint16": true },
"i32": { "TypeDef:Int32": true },
"u32": { "TypeDef:Uint32": true },
"i64": { "TypeDef:Int64": true },
"u64": { "TypeDef:Uint64": true },
"f32": { "TypeDef:Float32": true },
"f64": { "TypeDef:Float64": true },
"b": { "TypeDef:Boolean": true },
"e": { "TypeDef:Enum": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
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, "i8", &FieldValue::I8(-127))?;
engine.write_field(&mut buffer, "u8", &FieldValue::U8(0xAB))?;
engine.write_field(&mut buffer, "i16", &FieldValue::I16(-32000))?;
engine.write_field(&mut buffer, "u16", &FieldValue::U16(0xBEEF))?;
engine.write_field(&mut buffer, "i32", &FieldValue::I32(-2_000_000_007))?;
engine.write_field(&mut buffer, "u32", &FieldValue::U32(0xDEADBEEF))?;
engine.write_field(&mut buffer, "i64", &FieldValue::I64(-9_000_000_000_000_000_000))?;
engine.write_field(&mut buffer, "u64", &FieldValue::U64(0x0102030405060708))?;
engine.write_field(&mut buffer, "f32", &FieldValue::F32(1.5))?;
engine.write_field(&mut buffer, "f64", &FieldValue::F64(2.5))?;
engine.write_field(&mut buffer, "b", &FieldValue::Bool(true))?;
engine.write_field(&mut buffer, "e", &FieldValue::Enum(7))?;
assert_eq!(engine.read_field(&buffer, "i8")?, FieldValue::I8(-127));
assert_eq!(engine.read_field(&buffer, "u8")?, FieldValue::U8(0xAB));
assert_eq!(engine.read_field(&buffer, "i16")?, FieldValue::I16(-32000));
assert_eq!(engine.read_field(&buffer, "u16")?, FieldValue::U16(0xBEEF));
assert_eq!(
engine.read_field(&buffer, "i32")?,
FieldValue::I32(-2_000_000_007)
);
assert_eq!(
engine.read_field(&buffer, "u32")?,
FieldValue::U32(0xDEADBEEF)
);
assert_eq!(
engine.read_field(&buffer, "i64")?,
FieldValue::I64(-9_000_000_000_000_000_000)
);
assert_eq!(
engine.read_field(&buffer, "u64")?,
FieldValue::U64(0x0102030405060708)
);
assert_eq!(engine.read_field(&buffer, "f32")?, FieldValue::F32(1.5));
assert_eq!(engine.read_field(&buffer, "f64")?, FieldValue::F64(2.5));
assert_eq!(engine.read_field(&buffer, "b")?, FieldValue::Bool(true));
assert_eq!(engine.read_field(&buffer, "e")?, FieldValue::Enum(7));
Ok(())
}
#[test]
fn read_write_aligned_round_trips_string() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"name": { "TypeDef:String": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
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 world"))?;
assert_eq!(
engine.read_field(&buffer, "name")?,
FieldValue::String("hello world")
);
Ok(())
}
#[test]
fn read_write_aligned_round_trips_bytes() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"blob": { "TypeDef:Bytes": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let offset_map = engine.offset_map().expect("aligned mode has offset_map");
let payload = b"the quick brown fox".to_vec();
let mut buffer = vec![0u8; offset_map.total_size() + payload.len()];
engine.write_field(&mut buffer, "blob", &FieldValue::Bytes(&payload))?;
assert_eq!(
engine.read_field(&buffer, "blob")?,
FieldValue::Bytes(&payload)
);
Ok(())
}
#[test]
fn read_field_returns_access_error_in_packed_mode() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
let buffer = [0u8; 4];
let err = engine.read_field(&buffer, "id").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn write_field_returns_access_error_in_packed_mode() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed)?;
let mut buffer = [0u8; 4];
let err = engine
.write_field(&mut buffer, "id", &FieldValue::U32(1))
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_field_returns_offset_error_for_missing_path() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let buffer = [0u8; 8];
let err = engine.read_field(&buffer, "missing").unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
Ok(())
}
#[test]
fn write_field_returns_offset_error_for_missing_path() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let mut buffer = [0u8; 8];
let err = engine
.write_field(&mut buffer, "missing", &FieldValue::U32(1))
.unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_field_returns_access_error_for_composite_types() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"vals": {
"TypeDef:Array": true,
"items": { "TypeDef:Uint32": true }
}
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let buffer = [0u8; 8];
let err = engine.read_field(&buffer, "vals").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn write_field_returns_access_error_for_composite_value() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let mut buffer = [0u8; 8];
let err = engine
.write_field(&mut buffer, "id", &FieldValue::Struct { start: 0, end: 4 })
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_field_aligned_reads_nested_struct_byte_range() -> Result<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"header": {
"TypeDef:Struct": true,
"properties": {
"version": { "TypeDef:Uint8": true },
"magic": { "TypeDef:Uint32": true }
}
}
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
let offset_map = engine.offset_map().expect("aligned mode");
let mut buffer = vec![0u8; offset_map.total_size()];
engine.write_field(&mut buffer, "header.version", &FieldValue::U8(3))?;
engine.write_field(&mut buffer, "header.magic", &FieldValue::U32(0xCAFEBABE))?;
assert_eq!(
engine.read_field(&buffer, "header.version")?,
FieldValue::U8(3)
);
assert_eq!(
engine.read_field(&buffer, "header.magic")?,
FieldValue::U32(0xCAFEBABE)
);
Ok(())
}
+415
View File
@@ -0,0 +1,415 @@
//! Error path integration tests for `alknet-typedef`.
//!
//! Exercises the `TypedefError` variants across the crate:
//! `Access` (buffer too short, invalid UTF-8, invalid boolean byte,
//! unknown discriminator value), `Schema` (missing TypeDef kind,
//! malformed discriminator annotation), and `Offset` (missing
//! variable-length field size in `LayoutBuilder::build`).
use alknet_typedef::data_access;
use alknet_typedef::tunion;
use alknet_typedef::*;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn read_u32_buffer_too_short_returns_access_error() {
let buffer = [0u8; 2];
let err = data_access::read_u32(&buffer, 0, "header.id", Endian::Little).unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
assert_eq!(field_path, "header.id");
assert!(reason.contains("bounds"), "reason: {reason}");
}
other => panic!("expected Access, got {other:?}"),
}
}
#[test]
fn read_u16_buffer_too_short_returns_access_error() {
let buffer = [0u8; 1];
let err = data_access::read_u16(&buffer, 0, "tag", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_u64_buffer_too_short_returns_access_error() {
let buffer = [0u8; 4];
let err = data_access::read_u64(&buffer, 0, "offset", Endian::Big).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_f32_buffer_too_short_returns_access_error() {
let buffer = [0u8; 2];
let err = data_access::read_f32(&buffer, 0, "score", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_f64_buffer_too_short_returns_access_error() {
let buffer = [0u8; 4];
let err = data_access::read_f64(&buffer, 0, "score", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_i32_buffer_too_short_returns_access_error() {
let buffer = [0u8; 2];
let err = data_access::read_i32(&buffer, 0, "id", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_bool_buffer_too_short_returns_access_error() {
let buffer: [u8; 0] = [];
let err = data_access::read_bool(&buffer, 0, "flag").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_string_buffer_too_short_on_prefix_returns_access_error() {
let buffer = [0u8; 2];
let err = data_access::read_string(&buffer, 0, "name", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_string_buffer_too_short_on_data_returns_access_error() {
let mut buffer = vec![0u8; 6];
buffer[0..4].copy_from_slice(&100u32.to_le_bytes());
let err = data_access::read_string(&buffer, 0, "name", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_bytes_buffer_too_short_on_data_returns_access_error() {
let mut buffer = vec![0u8; 5];
buffer[0..4].copy_from_slice(&100u32.to_le_bytes());
let err = data_access::read_bytes(&buffer, 0, "blob", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_string_invalid_utf8_returns_access_error() {
let mut buffer = vec![0u8; 16];
let invalid = [0xFFu8, 0xFE, 0xFD];
let _ = data_access::write_bytes(&mut buffer, 0, &invalid, "name", Endian::Little);
let err = data_access::read_string(&buffer, 0, "name", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_bool_invalid_byte_returns_access_error() {
let buffer = [0x02u8];
let err = data_access::read_bool(&buffer, 0, "flag").unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
assert_eq!(field_path, "flag");
assert!(reason.contains("0x02"), "reason: {reason}");
}
other => panic!("expected Access, got {other:?}"),
}
}
#[test]
fn read_bool_zero_is_false() -> Result<(), TypedefError> {
let buffer = [0x00u8];
assert!(!data_access::read_bool(&buffer, 0, "flag")?);
Ok(())
}
#[test]
fn read_bool_one_is_true() -> Result<(), TypedefError> {
let buffer = [0x01u8];
assert!(data_access::read_bool(&buffer, 0, "flag")?);
Ok(())
}
#[test]
fn read_bool_three_is_access_error() {
let buffer = [0x03u8];
let err = data_access::read_bool(&buffer, 0, "flag").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn write_u32_buffer_too_short_returns_access_error() {
let mut buffer = [0u8; 2];
let err = data_access::write_u32(&mut buffer, 0, 1, "id", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn write_string_buffer_too_short_returns_access_error() {
let mut buffer = vec![0u8; 4];
let err =
data_access::write_string(&mut buffer, 0, "hello", "name", Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn compile_missing_typedef_kind_returns_schema_error() {
let mut schema = json!({ "type": "object", "properties": {} });
let err = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn offset_map_compute_missing_typedef_kind_returns_schema_error() {
let schema = json!({ "type": "object", "properties": {} });
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn offset_map_compute_non_struct_top_level_returns_schema_error() {
let schema = json!({ "TypeDef:Uint32": true });
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn layout_builder_new_missing_typedef_kind_returns_schema_error() {
let schema = json!({ "type": "object", "properties": {} });
let err = LayoutBuilder::new(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn layout_builder_new_non_struct_top_level_returns_schema_error() {
let schema = json!({ "TypeDef:Uint32": true });
let err = LayoutBuilder::new(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn parse_discriminator_missing_returns_schema_error() {
let schema = json!({"TypeDef:Union": true});
let err = parse_discriminator(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn parse_discriminator_field_missing_name_returns_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field"}
});
let err = parse_discriminator(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn parse_discriminator_unknown_kind_returns_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "magic"}
});
let err = parse_discriminator(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn parse_discriminator_byte_invalid_type_returns_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Float32"}
});
let err = parse_discriminator(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn read_byte_discriminator_unknown_value_returns_access_error() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint8"},
"mapping": {"5": {"TypeDef:Struct": true, "properties": {"x": {"TypeDef:Uint8": true}}}}
});
let buffer = [99u8, 0x00, 0x00];
let err = tunion::read_byte_discriminator(&buffer, &union_schema, Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_byte_discriminator_buffer_too_short_returns_access_error() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "offset": 4, "type": "TypeDef:Uint32"},
"mapping": {"5": {"TypeDef:Struct": true, "properties": {"x": {"TypeDef:Uint8": true}}}}
});
let buffer = [0u8; 2];
let err = tunion::read_byte_discriminator(&buffer, &union_schema, Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_field_discriminator_unknown_value_returns_access_error() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "type"},
"properties": {"type": {"TypeDef:Uint8": true}},
"mapping": {"0": {"TypeDef:Struct": true, "properties": {"x": {"TypeDef:Uint8": true}}}}
});
let mut buffer = vec![0u8; 8];
buffer[0] = 99;
let err =
tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn layout_builder_missing_var_size_returns_offset_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"name": { "TypeDef:String": true }
}
});
let builder = LayoutBuilder::new(&schema).expect("builder");
let empty: HashMap<String, usize> = HashMap::new();
let err = builder.build(&empty).unwrap_err();
match err {
TypedefError::Offset { field_path, reason } => {
assert_eq!(field_path, "name");
assert!(
reason.contains("missing variable-length field size"),
"reason: {reason}"
);
}
other => panic!("expected Offset, got {other:?}"),
}
}
#[test]
fn layout_builder_missing_array_data_size_returns_offset_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"vals": {
"TypeDef:Array": true,
"items": { "TypeDef:Uint32": true }
}
}
});
let builder = LayoutBuilder::new(&schema).expect("builder");
let empty: HashMap<String, usize> = HashMap::new();
let err = builder.build(&empty).unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
}
#[test]
fn layout_builder_missing_discriminator_value_returns_offset_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"payload": {
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint8"},
"mapping": {"5": {"$ref": "#/$defs/Read"}}
}
},
"$defs": {
"Read": {"TypeDef:Struct": true, "properties": {"x": {"TypeDef:Uint8": true}}}
}
});
let builder = LayoutBuilder::new(&schema).expect("builder");
let empty: HashMap<String, usize> = HashMap::new();
let err = builder.build(&empty).unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
}
#[test]
fn layout_builder_unknown_discriminator_value_returns_offset_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"payload": {
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint8"},
"mapping": {"5": {"$ref": "#/$defs/Read"}}
}
},
"$defs": {
"Read": {"TypeDef:Struct": true, "properties": {"x": {"TypeDef:Uint8": true}}}
}
});
let builder = LayoutBuilder::new(&schema).expect("builder");
let mut vs = HashMap::new();
vs.insert("payload.__discriminator".to_string(), 99);
let err = builder.build(&vs).unwrap_err();
match err {
TypedefError::Offset { reason, .. } => {
assert!(reason.contains("99"), "reason: {reason}");
}
other => panic!("expected Offset, got {other:?}"),
}
}
#[test]
fn sequential_reader_buffer_too_short_returns_access_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true }
}
});
let buffer = [0u8; 2];
let mut reader = SequentialReader::new(&schema).unwrap();
let err = reader.read_next(&buffer).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn sequential_reader_unknown_field_returns_schema_error() {
let schema = json!({
"TypeDef:Struct": true,
"properties": { "a": { "TypeDef:Uint8": true } }
});
let buffer = [0u8; 4];
let mut reader = SequentialReader::new(&schema).unwrap();
let err = reader.read_field(&buffer, "missing").unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn sequential_reader_new_non_struct_returns_schema_error() {
let schema = json!({ "TypeDef:Uint32": true });
let err = SequentialReader::new(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn read_string_indirect_data_region_too_short_returns_access_error() {
let mut index = [0u8; 8];
let _ = data_access::write_u32(&mut index, 0, 100, "idx.off", Endian::Little);
let _ = data_access::write_u32(&mut index, 4, 10, "idx.len", Endian::Little);
let data_region = b"too short";
let err = data_access::read_bytes_indirect(&index, 0, data_region, "blob", Endian::Little)
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_bytes_indirect_index_too_short_returns_access_error() {
let buffer = [0u8; 4];
let data_region = b"anything";
let err = data_access::read_bytes_indirect(&buffer, 0, data_region, "blob", Endian::Little)
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
#[test]
fn read_string_indirect_invalid_utf8_returns_access_error() {
let data_region: &[u8] = &[0xFF, 0xFE, 0xFD];
let mut index = [0u8; 8];
let _ = data_access::write_u32(&mut index, 0, 0, "idx.off", Endian::Little);
let _ = data_access::write_u32(&mut index, 4, 3, "idx.len", Endian::Little);
let err = data_access::read_string_indirect(&index, 0, data_region, "name", Endian::Little)
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
}
+535
View File
@@ -0,0 +1,535 @@
//! POC round-trip tests adapted from `/workspace/alknet-typedef-poc/`.
//!
//! 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.
use alknet_typedef::data_access;
use alknet_typedef::tunion;
use alknet_typedef::*;
use serde_json::json;
use std::collections::HashMap;
fn var_sizes(pairs: &[(&str, usize)]) -> HashMap<String, usize> {
pairs.iter().map(|(k, v)| (k.to_string(), *v)).collect()
}
#[test]
fn fixed_size_round_trip_via_offset_map() -> Result<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true },
"score": { "TypeDef:Float32": true },
"flag": { "TypeDef:Uint8": true },
"count": { "TypeDef:Uint16": true }
}
});
let offset_map = OffsetMap::compute(&schema)?;
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<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"id": { "TypeDef:Uint32": true },
"score": { "TypeDef:Float32": true },
"flag": { "TypeDef:Uint8": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
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<(), TypedefError> {
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<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"name": { "TypeDef:String": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
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<(), TypedefError> {
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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"header": {
"TypeDef:Struct": true,
"properties": {
"version": { "TypeDef:Uint32": true },
"magic": { "TypeDef:Uint32": true }
}
},
"payload": { "TypeDef:Bytes": true }
}
});
let offset_map = OffsetMap::compute(&schema)?;
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<(), TypedefError> {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": {
"header": {
"TypeDef:Struct": true,
"properties": {
"version": { "TypeDef:Uint8": true },
"flags": { "TypeDef:Uint8": true }
}
},
"payload_len": { "TypeDef:Uint32": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned)?;
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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"endian": "big",
"properties": {
"id": { "TypeDef:Uint32": true },
"offset": { "TypeDef:Float64": true }
}
});
let offset_map = OffsetMap::compute(&schema)?;
let endian = Endian::from_schema(&schema);
assert_eq!(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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true }
}
});
let offset_map = OffsetMap::compute(&schema)?;
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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true },
"payload": { "TypeDef:String": true }
}
});
let builder = LayoutBuilder::new(&schema)?;
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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"id": { "TypeDef:Uint8": true },
"name": { "TypeDef:String": true },
"tail": { "TypeDef:Uint8": true }
}
});
let builder = LayoutBuilder::new(&schema)?;
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 mut reader = SequentialReader::new(&schema)?;
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<(), TypedefError> {
let schema = json!({
"TypeDef:Struct": true,
"endian": "little",
"properties": {
"a": { "TypeDef:Uint8": true },
"b": { "TypeDef:Uint32": true },
"c": { "TypeDef:Uint8": true }
}
});
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 mut reader = SequentialReader::new(&schema)?;
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<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {
"kind": "byte",
"offset": 0,
"type": "TypeDef:Uint8"
},
"mapping": {
"5": { "$ref": "#/$defs/Read" },
"6": { "$ref": "#/$defs/Write" }
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true }
}
},
"Write": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true },
"data": { "TypeDef:Uint32": true }
}
}
}
});
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_schema, 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_schema, &dispatch.key)?;
assert_eq!(
variant
.get("TypeDef:Struct")
.and_then(serde_json::Value::as_bool),
Some(true)
);
Ok(())
}
#[test]
fn tunion_byte_offset_discriminator_size_lookup() -> Result<(), TypedefError> {
let u8_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint8"},
"mapping": {}
});
let u16_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint16"},
"mapping": {}
});
let u32_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint32"},
"mapping": {}
});
assert_eq!(tunion::discriminator_size(&u8_schema)?, 1);
assert_eq!(tunion::discriminator_size(&u16_schema)?, 2);
assert_eq!(tunion::discriminator_size(&u32_schema)?, 4);
Ok(())
}
+323
View File
@@ -0,0 +1,323 @@
//! Integration tests for TUnion discriminator dispatch.
//!
//! Exercises both discriminator kinds end-to-end: byte-offset (SFTP
//! pattern) and field-name (typedef.ts pattern). Verifies that
//! `read_byte_discriminator` / `read_field_discriminator` produce the
//! correct mapping key and variant offset, that `resolve_variant`
//! follows `$ref` pointers, and that `discriminator_size` reports the
//! right fixed sizes.
use alknet_typedef::data_access;
use alknet_typedef::tunion;
use alknet_typedef::{Endian, TypedefError};
use serde_json::json;
fn sftp_like_byte_union() -> serde_json::Value {
json!({
"TypeDef:Union": true,
"discriminator": {
"kind": "byte",
"offset": 0,
"type": "TypeDef:Uint8"
},
"mapping": {
"5": { "$ref": "#/$defs/Read" },
"6": { "$ref": "#/$defs/Write" }
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true }
}
},
"Write": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true },
"data": { "TypeDef:Uint32": true }
}
}
}
})
}
#[test]
fn read_byte_discriminator_uint8_dispatches_to_read() -> Result<(), TypedefError> {
let union_schema = sftp_like_byte_union();
let mut buffer = vec![0u8; 16];
buffer[0] = 5;
data_access::write_u32(&mut buffer, 1, 0x01020304, "Read.handle", Endian::Big)?;
let dispatch = tunion::read_byte_discriminator(&buffer, &union_schema, 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_schema, &dispatch.key)?;
assert_eq!(
variant
.get("TypeDef:Struct")
.and_then(serde_json::Value::as_bool),
Some(true)
);
Ok(())
}
#[test]
fn read_byte_discriminator_uint8_dispatches_to_write() -> Result<(), TypedefError> {
let union_schema = sftp_like_byte_union();
let mut buffer = vec![0u8; 16];
buffer[0] = 6;
data_access::write_u32(&mut buffer, 1, 0xDEADBEEF, "Write.handle", Endian::Big)?;
let dispatch = tunion::read_byte_discriminator(&buffer, &union_schema, Endian::Big)?;
assert_eq!(dispatch.key, "6");
assert_eq!(dispatch.variant_offset, 1);
assert_eq!(dispatch.discriminator_size, 1);
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
let props = variant
.get("properties")
.and_then(serde_json::Value::as_object)
.expect("variant has properties");
assert!(props.contains_key("data"));
Ok(())
}
#[test]
fn read_byte_discriminator_uint16_little_endian() -> Result<(), TypedefError> {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {
"kind": "byte",
"offset": 2,
"type": "TypeDef:Uint16"
},
"mapping": {
"5": {"TypeDef:Struct": true, "properties": {"id": {"TypeDef:Uint32": true}}}
}
});
let mut buffer = vec![0u8; 16];
buffer[2..4].copy_from_slice(&5u16.to_le_bytes());
let dispatch = tunion::read_byte_discriminator(&buffer, &schema, Endian::Little)?;
assert_eq!(dispatch.key, "5");
assert_eq!(dispatch.variant_offset, 4);
assert_eq!(dispatch.discriminator_size, 2);
Ok(())
}
#[test]
fn read_byte_discriminator_uint32_big_endian() -> Result<(), TypedefError> {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {
"kind": "byte",
"offset": 0,
"type": "TypeDef:Uint32"
},
"mapping": {
"101": {"TypeDef:Struct": true, "properties": {"id": {"TypeDef:Uint32": true}}}
}
});
let mut buffer = vec![0u8; 16];
buffer[0..4].copy_from_slice(&101u32.to_be_bytes());
let dispatch = tunion::read_byte_discriminator(&buffer, &schema, Endian::Big)?;
assert_eq!(dispatch.key, "101");
assert_eq!(dispatch.variant_offset, 4);
assert_eq!(dispatch.discriminator_size, 4);
Ok(())
}
#[test]
fn read_byte_discriminator_unknown_value_returns_access_error() -> Result<(), TypedefError> {
let union_schema = sftp_like_byte_union();
let buffer = [99u8, 0x00, 0x00, 0x00];
let err = tunion::read_byte_discriminator(&buffer, &union_schema, Endian::Big).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn read_field_discriminator_string_dispatches_to_read() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "type"},
"properties": {
"type": { "TypeDef:String": true }
},
"mapping": {
"read": {"$ref": "#/$defs/Read"},
"write": {"$ref": "#/$defs/Write"}
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true }
}
},
"Write": {
"TypeDef:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"data": { "TypeDef:Bytes": true }
}
}
}
});
let value = "read";
let mut buffer = vec![0u8; 32];
data_access::write_string(&mut buffer, 0, value, "type", Endian::Little)?;
let dispatch = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little)?;
assert_eq!(dispatch.key, "read");
assert_eq!(dispatch.variant_offset, 4 + value.len());
assert_eq!(dispatch.discriminator_size, 4 + value.len());
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
assert_eq!(
variant
.get("TypeDef:Struct")
.and_then(serde_json::Value::as_bool),
Some(true)
);
Ok(())
}
#[test]
fn read_field_discriminator_string_dispatches_to_write() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "type"},
"properties": {
"type": { "TypeDef:String": true }
},
"mapping": {
"read": {"$ref": "#/$defs/Read"},
"write": {"$ref": "#/$defs/Write"}
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"properties": {"x": {"TypeDef:Uint8": true}}
},
"Write": {
"TypeDef:Struct": true,
"properties": {"y": {"TypeDef:Uint16": true}}
}
}
});
let value = "write";
let mut buffer = vec![0u8; 32];
data_access::write_string(&mut buffer, 0, value, "type", Endian::Little)?;
let dispatch = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little)?;
assert_eq!(dispatch.key, "write");
assert_eq!(dispatch.variant_offset, 4 + value.len());
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
let props = variant
.get("properties")
.and_then(serde_json::Value::as_object)
.expect("variant has properties");
assert!(props.contains_key("y"));
assert!(!props.contains_key("x"));
Ok(())
}
#[test]
fn read_field_discriminator_uint8_field() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "tag"},
"properties": {
"tag": { "TypeDef:Uint8": true }
},
"mapping": {
"0": {"TypeDef:Struct": true, "properties": {"a": {"TypeDef:Uint32": true}}},
"1": {"TypeDef:Struct": true, "properties": {"b": {"TypeDef:Uint16": true}}}
}
});
let mut buffer = vec![0u8; 8];
buffer[0] = 0;
let dispatch = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little)?;
assert_eq!(dispatch.key, "0");
assert_eq!(dispatch.variant_offset, 1);
assert_eq!(dispatch.discriminator_size, 1);
buffer[0] = 1;
let dispatch = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little)?;
assert_eq!(dispatch.key, "1");
Ok(())
}
#[test]
fn read_field_discriminator_unknown_value_returns_access_error() -> Result<(), TypedefError> {
let union_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "tag"},
"properties": {
"tag": { "TypeDef:Uint8": true }
},
"mapping": {
"0": {"TypeDef:Struct": true, "properties": {"a": {"TypeDef:Uint32": true}}}
}
});
let mut buffer = vec![0u8; 8];
buffer[0] = 99;
let err =
tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
Ok(())
}
#[test]
fn discriminator_size_returns_correct_values() -> Result<(), TypedefError> {
let u8_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint8"},
"mapping": {}
});
let u16_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint16"},
"mapping": {}
});
let u32_schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "byte", "type": "TypeDef:Uint32"},
"mapping": {}
});
assert_eq!(tunion::discriminator_size(&u8_schema)?, 1);
assert_eq!(tunion::discriminator_size(&u16_schema)?, 2);
assert_eq!(tunion::discriminator_size(&u32_schema)?, 4);
Ok(())
}
#[test]
fn discriminator_size_field_kind_returns_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"discriminator": {"kind": "field", "name": "type"},
"properties": {"type": {"TypeDef:Uint8": true}},
"mapping": {}
});
let err = tunion::discriminator_size(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn resolve_variant_returns_schema_error_for_unknown_key() {
let union_schema = sftp_like_byte_union();
let err = tunion::resolve_variant(&union_schema, "999").unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}
#[test]
fn parse_discriminator_missing_returns_schema_error() {
let schema = json!({"TypeDef:Union": true});
let err = alknet_typedef::parse_discriminator(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
}