test(typedef/tests): add comprehensive integration tests and POC round-trip tests
This commit is contained in:
1 parent
db8f5d863d
commit
937df02f85
5 files changed
+1587
-2
No files matched your search
@@ -0,0 +1,371 @@
|
||||
//! 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 },
|
||||
"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, "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, "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(())
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
//! 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::*;
|
||||
use alknet_typedef::tunion;
|
||||
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:?}");
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
//! 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::*;
|
||||
use alknet_typedef::data_access;
|
||||
use alknet_typedef::tunion;
|
||||
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(())
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
//! 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:?}");
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: typedef/tests
|
||||
name: "Write comprehensive tests for alknet-typedef: unit tests, integration tests, and POC round-trip tests"
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [typedef/engine]
|
||||
scope: moderate
|
||||
risk: low
|
||||
@@ -189,4 +189,4 @@ Tests can be organized as:
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Added 86 integration tests across 4 files in `crates/alknet-typedef/tests/` (engine_integration: 20, error_paths: 39, poc_roundtrip: 14, tunion_dispatch: 13) exercising the public API end-to-end. Combined with the pre-existing 202 unit tests, the crate now has 288 tests covering all 17 TypeDef kinds, both layout modes, TUnion byte-offset and field-name discriminators, validation, and error paths (buffer-too-short, invalid UTF-8, invalid boolean, missing var-sizes, malformed discriminators). The POC round-trip patterns — fixed-size, string, nested struct, big-endian, alignment padding, packed layout, and sequential reader — are replicated and passing. All four verification commands pass: `cargo check --all-targets`, `cargo test -p alknet-typedef`, `cargo build --workspace`, and `cargo clippy` on the new test files (pre-existing source-file clippy errors in `src/sequential_reader.rs:855` and `src/validation.rs:608/691` are unrelated to this task).
|
||||
Reference in new issue
Block a user