//! 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 alktype::data_access; use alktype::tunion; use alktype::{Endian, AlkTypeError}; use serde_json::json; fn sftp_like_byte_union() -> serde_json::Value { json!({ "AlkType:Union": true, "discriminator": { "kind": "byte", "offset": 0, "type": "AlkType:Uint8" }, "mapping": { "5": { "$ref": "#/$defs/Read" }, "6": { "$ref": "#/$defs/Write" } }, "$defs": { "Read": { "AlkType:Struct": true, "properties": { "handle": { "AlkType:Uint32": true }, "length": { "AlkType:Uint32": true } } }, "Write": { "AlkType:Struct": true, "properties": { "handle": { "AlkType:Uint32": true }, "length": { "AlkType:Uint32": true }, "data": { "AlkType:Uint32": true } } } } }) } #[test] fn read_byte_discriminator_uint8_dispatches_to_read() -> Result<(), AlkTypeError> { 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("AlkType:Struct") .and_then(serde_json::Value::as_bool), Some(true) ); Ok(()) } #[test] fn read_byte_discriminator_uint8_dispatches_to_write() -> Result<(), AlkTypeError> { 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<(), AlkTypeError> { let schema = json!({ "AlkType:Union": true, "discriminator": { "kind": "byte", "offset": 2, "type": "AlkType:Uint16" }, "mapping": { "5": {"AlkType:Struct": true, "properties": {"id": {"AlkType: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<(), AlkTypeError> { let schema = json!({ "AlkType:Union": true, "discriminator": { "kind": "byte", "offset": 0, "type": "AlkType:Uint32" }, "mapping": { "101": {"AlkType:Struct": true, "properties": {"id": {"AlkType: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<(), AlkTypeError> { 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, AlkTypeError::Access { .. }), "got {err:?}"); Ok(()) } #[test] fn read_field_discriminator_string_dispatches_to_read() -> Result<(), AlkTypeError> { let union_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "field", "name": "type"}, "properties": { "type": { "AlkType:String": true } }, "mapping": { "read": {"$ref": "#/$defs/Read"}, "write": {"$ref": "#/$defs/Write"} }, "$defs": { "Read": { "AlkType:Struct": true, "properties": { "handle": { "AlkType:Uint32": true }, "length": { "AlkType:Uint32": true } } }, "Write": { "AlkType:Struct": true, "properties": { "handle": { "AlkType:Uint32": true }, "data": { "AlkType: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("AlkType:Struct") .and_then(serde_json::Value::as_bool), Some(true) ); Ok(()) } #[test] fn read_field_discriminator_string_dispatches_to_write() -> Result<(), AlkTypeError> { let union_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "field", "name": "type"}, "properties": { "type": { "AlkType:String": true } }, "mapping": { "read": {"$ref": "#/$defs/Read"}, "write": {"$ref": "#/$defs/Write"} }, "$defs": { "Read": { "AlkType:Struct": true, "properties": {"x": {"AlkType:Uint8": true}} }, "Write": { "AlkType:Struct": true, "properties": {"y": {"AlkType: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<(), AlkTypeError> { let union_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "field", "name": "tag"}, "properties": { "tag": { "AlkType:Uint8": true } }, "mapping": { "0": {"AlkType:Struct": true, "properties": {"a": {"AlkType:Uint32": true}}}, "1": {"AlkType:Struct": true, "properties": {"b": {"AlkType: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<(), AlkTypeError> { let union_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "field", "name": "tag"}, "properties": { "tag": { "AlkType:Uint8": true } }, "mapping": { "0": {"AlkType:Struct": true, "properties": {"a": {"AlkType: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, AlkTypeError::Access { .. }), "got {err:?}"); Ok(()) } #[test] fn discriminator_size_returns_correct_values() -> Result<(), AlkTypeError> { let u8_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "byte", "type": "AlkType:Uint8"}, "mapping": {} }); let u16_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "byte", "type": "AlkType:Uint16"}, "mapping": {} }); let u32_schema = json!({ "AlkType:Union": true, "discriminator": {"kind": "byte", "type": "AlkType: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!({ "AlkType:Union": true, "discriminator": {"kind": "field", "name": "type"}, "properties": {"type": {"AlkType:Uint8": true}}, "mapping": {} }); let err = tunion::discriminator_size(&schema).unwrap_err(); assert!(matches!(err, AlkTypeError::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, AlkTypeError::Schema(_)), "got {err:?}"); } #[test] fn parse_discriminator_missing_returns_schema_error() { let schema = json!({"AlkType:Union": true}); let err = alktype::parse_discriminator(&schema).unwrap_err(); assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); }