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:
1 parent
eac7ad88b3
commit
2c4a4994dc
36 files changed
+13805
No files matched your search
@@ -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(())
|
||||
}
|
||||
@@ -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:?}");
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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:?}");
|
||||
}
|
||||
Reference in new issue
Block a user