refactor(typedef): introduce TypeDefKind enum for integer dispatch

Replace all string-based TypeDef:* kind matching with a 17-variant
TypeDefKind enum. The enum provides compile-time exhaustiveness
checking, integer discriminant dispatch (jump table), and type-safe
methods (type_size, natural_alignment, is_fixed_size, etc.).

- Add TypeDefKind enum with FromStr, Display, and helper methods
- Add get_typedef_kind_enum() and get_typedef_kind_loose_enum()
- Convert DiscriminatorKind::Byte.disc_type from String to TypeDefKind
- Convert FieldPosition.kind from String to TypeDefKind
- Convert all 8 dispatch sites from string matching to enum matching
- Remove legacy string-based type_size/natural_alignment/is_fixed_size
  wrapper functions — all call sites use enum methods directly
- Update tests to use enum variants
This commit is contained in:
deepseek-v4-pro committed 2026-07-21 12:07:39 +00:00
1 parent 72aa79b6ee
commit ce7ef1e31f
8 files changed
+465 -300

No files matched your search

+25 -24
View File
@@ -13,7 +13,7 @@ use crate::data_access;
use crate::error::TypedefError;
use crate::layout_builder::LayoutBuilder;
use crate::offset_map::OffsetMap;
use crate::schema::{self, get_typedef_kind_loose, Endian};
use crate::schema::{self, get_typedef_kind_loose_enum, Endian, TypeDefKind};
use crate::sequential_reader::{FieldValue, SequentialReader};
use crate::validation;
use serde_json::Value;
@@ -206,82 +206,80 @@ impl TypedefEngine {
field_path: field_path.to_string(),
reason: "field schema not found in schema tree".to_string(),
})?;
let kind = get_typedef_kind_loose(field_schema).ok_or_else(|| TypedefError::Offset {
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "field schema has no TypeDef:* kind".to_string(),
})?;
let endian = self.endian;
match kind {
"TypeDef:Int8" => {
TypeDefKind::Int8 => {
let v = data_access::read_i8(buffer, range.start, field_path)?;
Ok(FieldValue::I8(v))
}
"TypeDef:Int16" => {
TypeDefKind::Int16 => {
let v = data_access::read_i16(buffer, range.start, field_path, endian)?;
Ok(FieldValue::I16(v))
}
"TypeDef:Int32" => {
TypeDefKind::Int32 => {
let v = data_access::read_i32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::I32(v))
}
"TypeDef:Uint8" => {
TypeDefKind::Uint8 => {
let v = data_access::read_u8(buffer, range.start, field_path)?;
Ok(FieldValue::U8(v))
}
"TypeDef:Uint16" => {
TypeDefKind::Uint16 => {
let v = data_access::read_u16(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U16(v))
}
"TypeDef:Uint32" => {
TypeDefKind::Uint32 => {
let v = data_access::read_u32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U32(v))
}
"TypeDef:Uint64" => {
TypeDefKind::Uint64 => {
let v = data_access::read_u64(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U64(v))
}
"TypeDef:Float32" => {
TypeDefKind::Float32 => {
let v = data_access::read_f32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::F32(v))
}
"TypeDef:Float64" => {
TypeDefKind::Float64 => {
let v = data_access::read_f64(buffer, range.start, field_path, endian)?;
Ok(FieldValue::F64(v))
}
"TypeDef:Boolean" => {
TypeDefKind::Boolean => {
let v = data_access::read_bool(buffer, range.start, field_path)?;
Ok(FieldValue::Bool(v))
}
"TypeDef:Enum" => {
TypeDefKind::Enum => {
let v = data_access::read_enum(buffer, range.start, field_path, endian)?;
Ok(FieldValue::Enum(v))
}
"TypeDef:String" => {
TypeDefKind::String => {
let v = data_access::read_string(buffer, range.start, field_path, endian)?;
Ok(FieldValue::String(v))
}
"TypeDef:Bytes" => {
TypeDefKind::Bytes => {
let v = data_access::read_bytes(buffer, range.start, field_path, endian)?;
Ok(FieldValue::Bytes(v))
}
"TypeDef:Timestamp" => {
TypeDefKind::Timestamp => {
let v = data_access::read_string(buffer, range.start, field_path, endian)?;
Ok(FieldValue::String(v))
}
"TypeDef:Struct" => Ok(FieldValue::Struct {
TypeDefKind::Struct => Ok(FieldValue::Struct {
start: range.start,
end: range.end,
}),
"TypeDef:Union" | "TypeDef:Array" | "TypeDef:Record" => Err(TypedefError::Access {
TypeDefKind::Union | TypeDefKind::Array | TypeDefKind::Record => {
Err(TypedefError::Access {
field_path: field_path.to_string(),
reason: "read_field does not support composite types; \
use the layout-specific APIs"
.to_string(),
}),
other => Err(TypedefError::Access {
field_path: field_path.to_string(),
reason: format!("unsupported TypeDef kind for read_field: {other}"),
}),
})
}
}
}
@@ -737,6 +735,9 @@ mod tests {
#[test]
fn typedef_kind_loose_recognizes_object_form() {
let node = json!({ "TypeDef:String": { "encoding": "offset-indirect" } });
assert_eq!(get_typedef_kind_loose(&node), Some("TypeDef:String"));
assert_eq!(
get_typedef_kind_loose_enum(&node),
Some(TypeDefKind::String)
);
}
}
+75 -74
View File
@@ -46,7 +46,7 @@
//! the variant struct.
use crate::error::TypedefError;
use crate::schema::{self, get_typedef_kind_loose, resolve_ref_or_inline, DiscriminatorKind, Endian, DISCRIMINATOR_PATH, U32_SIZE};
use crate::schema::{self, get_typedef_kind_loose_enum, resolve_ref_or_inline, DiscriminatorKind, Endian, TypeDefKind, DISCRIMINATOR_PATH, U32_SIZE};
use serde_json::Value;
use std::collections::HashMap;
@@ -60,9 +60,8 @@ pub struct FieldPosition {
/// Byte size of the field (4 for length prefix of variable-length
/// fields, actual size for fixed-size fields).
pub size: usize,
/// The TypeDef kind of the field (e.g., "TypeDef:Uint32",
/// "TypeDef:String").
pub kind: String,
/// The TypeDef kind of the field.
pub kind: TypeDefKind,
}
/// The result of building a layout: a map of field_path → FieldPosition
@@ -148,10 +147,12 @@ impl LayoutBuilder {
/// Returns [`TypedefError::Schema`] if the schema has no
/// `TypeDef:*` kind or the top-level kind is not `TypeDef:Struct`.
pub fn new(schema: &Value) -> Result<Self, TypedefError> {
let kind = schema::get_typedef_kind(schema).ok_or_else(|| {
let kind = schema::get_typedef_kind(schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| {
TypedefError::Schema("top-level schema has no TypeDef:* kind".to_string())
})?;
if kind != "TypeDef:Struct" {
if kind != TypeDefKind::Struct {
return Err(TypedefError::Schema(format!(
"LayoutBuilder requires a TypeDef:Struct at the top level, got {kind}"
)));
@@ -251,22 +252,25 @@ impl<'a> BuildCtx<'a> {
field_path: &str,
offset: &mut usize,
) -> Result<(), TypedefError> {
let kind = get_typedef_kind_loose(field_schema).ok_or_else(|| TypedefError::Offset {
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "field schema has no TypeDef:* kind".to_string(),
})?;
match kind {
"TypeDef:Struct" => self.walk_struct(field_schema, field_path, offset),
"TypeDef:Union" => self.walk_union(field_schema, field_path, offset),
"TypeDef:Array" => self.walk_array(field_schema, field_path, offset),
"TypeDef:String" | "TypeDef:Bytes" | "TypeDef:Timestamp" | "TypeDef:Record" => {
TypeDefKind::Struct => self.walk_struct(field_schema, field_path, offset),
TypeDefKind::Union => self.walk_union(field_schema, field_path, offset),
TypeDefKind::Array => self.walk_array(field_schema, field_path, offset),
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Timestamp
| TypeDefKind::Record => {
self.walk_variable(field_path, offset, kind)
}
fixed if schema::is_fixed_size(fixed) => {
let size = schema::type_size(fixed).ok_or_else(|| TypedefError::Offset {
k if k.is_fixed_size() => {
let size = k.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("type_size returned None for fixed kind {fixed}"),
reason: format!("type_size returned None for fixed kind {k}"),
})?;
let start = *offset;
*offset = start
@@ -275,13 +279,10 @@ impl<'a> BuildCtx<'a> {
field_path: field_path.to_string(),
reason: format!("offset {start} + size {size} overflows usize"),
})?;
self.push(field_path, start, size, fixed);
self.push(field_path, start, size, k);
Ok(())
}
other => Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("unsupported TypeDef kind for packed layout: {other}"),
}),
_ => unreachable!("all TypeDefKind variants are covered above"),
}
}
@@ -291,7 +292,7 @@ impl<'a> BuildCtx<'a> {
&mut self,
field_path: &str,
offset: &mut usize,
kind: &str,
kind: TypeDefKind,
) -> Result<(), TypedefError> {
let data_size =
self.var_sizes
@@ -341,12 +342,12 @@ impl<'a> BuildCtx<'a> {
field_path: field_path.to_string(),
reason: "could not resolve TArray items schema".to_string(),
})?;
let elem_kind = get_typedef_kind_loose(element_schema).ok_or_else(|| TypedefError::Offset {
let elem_kind = get_typedef_kind_loose_enum(element_schema).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TArray element schema has no TypeDef:* kind".to_string(),
})?;
if !schema::is_fixed_size(elem_kind) {
if !elem_kind.is_fixed_size() {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!(
@@ -355,7 +356,7 @@ impl<'a> BuildCtx<'a> {
});
}
let elem_size = schema::type_size(elem_kind).ok_or_else(|| TypedefError::Offset {
let elem_size = elem_kind.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("element kind {elem_kind} has no fixed size"),
})?;
@@ -434,7 +435,7 @@ impl<'a> BuildCtx<'a> {
field_path: field_path.to_string(),
reason: format!("offset {start} + total {total} overflows usize"),
})?;
self.push(field_path, start, U32_SIZE, "TypeDef:Array");
self.push(field_path, start, U32_SIZE, TypeDefKind::Array);
Ok(())
}
}
@@ -461,7 +462,7 @@ impl<'a> BuildCtx<'a> {
offset: disc_off,
disc_type,
} => self
.walk_byte_discriminator_union(field_path, offset, &disc_type, disc_off, mapping),
.walk_byte_discriminator_union(field_path, offset, disc_type, disc_off, mapping),
DiscriminatorKind::Field { name: _ } => {
self.walk_field_discriminator_union(field_path, offset, mapping)
}
@@ -473,11 +474,11 @@ impl<'a> BuildCtx<'a> {
&mut self,
field_path: &str,
offset: &mut usize,
disc_type: &str,
disc_type: TypeDefKind,
disc_off: usize,
mapping: &serde_json::Map<String, Value>,
) -> Result<(), TypedefError> {
let disc_size = schema::type_size(disc_type).ok_or_else(|| TypedefError::Offset {
let disc_size = disc_type.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("discriminator type {disc_type} has no fixed size"),
})?;
@@ -517,11 +518,11 @@ impl<'a> BuildCtx<'a> {
reason: "could not resolve TUnion variant schema ($ref not found)".to_string(),
}
})?;
let v_kind = get_typedef_kind_loose(resolved).ok_or_else(|| TypedefError::Offset {
let v_kind = get_typedef_kind_loose_enum(resolved).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TUnion variant schema has no TypeDef:* kind".to_string(),
})?;
if v_kind != "TypeDef:Struct" {
if v_kind != TypeDefKind::Struct {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("TUnion variant must be TypeDef:Struct, got {v_kind}"),
@@ -580,11 +581,11 @@ impl<'a> BuildCtx<'a> {
reason: "could not resolve TUnion variant schema ($ref not found)".to_string(),
}
})?;
let v_kind = get_typedef_kind_loose(resolved).ok_or_else(|| TypedefError::Offset {
let v_kind = get_typedef_kind_loose_enum(resolved).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TUnion variant schema has no TypeDef:* kind".to_string(),
})?;
if v_kind != "TypeDef:Struct" {
if v_kind != TypeDefKind::Struct {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("TUnion variant must be TypeDef:Struct, got {v_kind}"),
@@ -595,13 +596,13 @@ impl<'a> BuildCtx<'a> {
}
/// Push a `(field_path, FieldPosition)` pair onto the fields vec.
fn push(&mut self, path: &str, offset: usize, size: usize, kind: &str) {
fn push(&mut self, path: &str, offset: usize, size: usize, kind: TypeDefKind) {
self.fields.push((
path.to_string(),
FieldPosition {
offset,
size,
kind: kind.to_string(),
kind,
},
));
}
@@ -639,7 +640,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -647,7 +648,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -655,7 +656,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 2,
kind: "TypeDef:Uint16".into()
kind: TypeDefKind::Uint16
})
);
assert_eq!(layout.total_size(), 7);
@@ -677,7 +678,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -685,7 +686,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -693,7 +694,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 4,
kind: "TypeDef:String".into()
kind: TypeDefKind::String
})
);
assert_eq!(layout.total_size(), 19);
@@ -714,7 +715,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:String".into()
kind: TypeDefKind::String
})
);
assert_eq!(
@@ -722,7 +723,7 @@ mod tests {
Some(&FieldPosition {
offset: 9,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(layout.total_size(), 10);
@@ -742,7 +743,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Bytes".into()
kind: TypeDefKind::Bytes
})
);
assert_eq!(layout.total_size(), 7);
@@ -762,7 +763,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Timestamp".into()
kind: TypeDefKind::Timestamp
})
);
assert_eq!(layout.total_size(), 24);
@@ -785,7 +786,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Record".into()
kind: TypeDefKind::Record
})
);
assert_eq!(layout.total_size(), 104);
@@ -834,7 +835,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -842,7 +843,7 @@ mod tests {
Some(&FieldPosition {
offset: 4,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -850,7 +851,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 9);
@@ -877,7 +878,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -885,7 +886,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:String".into()
kind: TypeDefKind::String
})
);
assert_eq!(
@@ -893,7 +894,7 @@ mod tests {
Some(&FieldPosition {
offset: 8,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(layout.total_size(), 9);
@@ -918,7 +919,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -926,7 +927,7 @@ mod tests {
Some(&FieldPosition {
offset: 4,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -934,7 +935,7 @@ mod tests {
Some(&FieldPosition {
offset: 8,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 12);
@@ -960,7 +961,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -968,7 +969,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 2,
kind: "TypeDef:Uint16".into()
kind: TypeDefKind::Uint16
})
);
assert_eq!(
@@ -976,7 +977,7 @@ mod tests {
Some(&FieldPosition {
offset: 3,
size: 2,
kind: "TypeDef:Uint16".into()
kind: TypeDefKind::Uint16
})
);
assert_eq!(layout.total_size(), 5);
@@ -999,7 +1000,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:Array".into()
kind: TypeDefKind::Array
})
);
assert_eq!(layout.total_size(), 16);
@@ -1085,7 +1086,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1093,7 +1094,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -1101,7 +1102,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 9);
@@ -1150,7 +1151,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1158,7 +1159,7 @@ mod tests {
Some(&FieldPosition {
offset: 9,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 13);
@@ -1198,7 +1199,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1206,7 +1207,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(
@@ -1214,7 +1215,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 4,
kind: "TypeDef:String".into()
kind: TypeDefKind::String
})
);
assert_eq!(layout.total_size(), 17);
@@ -1323,7 +1324,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1331,7 +1332,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 5);
@@ -1376,7 +1377,7 @@ mod tests {
Some(&FieldPosition {
offset: 5,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 9);
@@ -1554,7 +1555,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 4,
kind: "TypeDef:String".into()
kind: TypeDefKind::String
})
);
assert_eq!(layout.total_size(), 9);
@@ -1632,7 +1633,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1640,7 +1641,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 5);
@@ -1680,7 +1681,7 @@ mod tests {
Some(&FieldPosition {
offset: 0,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1688,7 +1689,7 @@ mod tests {
Some(&FieldPosition {
offset: 1,
size: 1,
kind: "TypeDef:Uint8".into()
kind: TypeDefKind::Uint8
})
);
assert_eq!(
@@ -1696,7 +1697,7 @@ mod tests {
Some(&FieldPosition {
offset: 2,
size: 4,
kind: "TypeDef:Uint32".into()
kind: TypeDefKind::Uint32
})
);
assert_eq!(layout.total_size(), 6);
+3 -3
View File
@@ -38,9 +38,9 @@ pub use error::TypedefError;
pub use layout_builder::{FieldPosition, LayoutBuilder, PackedLayout};
pub use offset_map::{ByteRange, OffsetMap};
pub use schema::{
get_typedef_kind_loose, normalize_refs, parse_align, parse_discriminator, parse_encoding,
parse_endian, parse_max_length, resolve_ref, resolve_ref_or_inline, DiscriminatorKind, Endian,
VariableEncoding,
get_typedef_kind_loose, get_typedef_kind_loose_enum, normalize_refs, parse_align,
parse_discriminator, parse_encoding, parse_endian, parse_max_length, resolve_ref,
resolve_ref_or_inline, DiscriminatorKind, Endian, TypeDefKind, VariableEncoding,
};
pub use sequential_reader::{FieldValue, SequentialReader};
pub use tunion::UnionDispatch;
+3 -3
View File
@@ -1,8 +1,8 @@
//! Macros for generating repetitive code across the 17 TypeDef kinds.
//!
//! These macros eliminate boilerplate in validation and data access.
//! Each macro takes a compact specification and generates the full
//! implementation, ensuring consistency across all types.
//! These macros eliminate boilerplate in validation, data access, and
//! dispatch. Each macro takes a compact specification and generates the
//! full implementation, ensuring consistency across all types.
// ---------------------------------------------------------------------------
// Validation macros
+37 -29
View File
@@ -13,9 +13,9 @@
use crate::error::TypedefError;
use crate::schema::{
get_typedef_kind, get_typedef_kind_loose, is_fixed_size, natural_alignment, parse_align,
parse_discriminator, parse_encoding, parse_max_length, resolve_ref_or_inline, type_size,
DiscriminatorKind, VariableEncoding, DISCRIMINATOR_PATH,
get_typedef_kind, get_typedef_kind_loose_enum, parse_align, parse_discriminator, parse_encoding,
parse_max_length, resolve_ref_or_inline, DiscriminatorKind, TypeDefKind, VariableEncoding,
DISCRIMINATOR_PATH,
};
use serde_json::Value;
@@ -79,10 +79,12 @@ impl OffsetMap {
/// Returns [`TypedefError::Offset`] for unsupported type combinations
/// encountered during the walk.
pub fn compute(schema: &Value) -> Result<Self, TypedefError> {
let kind = get_typedef_kind(schema).ok_or_else(|| {
let kind = get_typedef_kind(schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| {
TypedefError::Schema("top-level schema has no TypeDef:* kind".to_string())
})?;
if kind != "TypeDef:Struct" {
if kind != TypeDefKind::Struct {
return Err(TypedefError::Schema(format!(
"OffsetMap::compute requires a TypeDef:Struct at the top level, got {kind}"
)));
@@ -207,47 +209,47 @@ impl<'a> ComputeCtx<'a> {
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
let kind = get_typedef_kind_loose(field_schema).ok_or_else(|| TypedefError::Offset {
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "field schema has no TypeDef:* kind".to_string(),
})?;
match kind {
"TypeDef:Struct" => {
TypeDefKind::Struct => {
self.compute_struct_field(field_schema, field_path, struct_default_align)
}
"TypeDef:Union" => {
TypeDefKind::Union => {
self.compute_union_field(field_schema, field_path, struct_default_align)
}
"TypeDef:Array" => {
TypeDefKind::Array => {
self.compute_array_field(field_schema, field_path, struct_default_align)
}
"TypeDef:String" | "TypeDef:Bytes" | "TypeDef:Record" | "TypeDef:Timestamp" => {
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Record
| TypeDefKind::Timestamp => {
self.compute_variable_field(field_schema, field_path, struct_default_align)
}
fixed if is_fixed_size(fixed) => {
self.compute_fixed_field(fixed, field_schema, field_path, struct_default_align)
k if k.is_fixed_size() => {
self.compute_fixed_field(k, field_schema, field_path, struct_default_align)
}
other => Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("unsupported TypeDef kind for offset computation: {other}"),
}),
_ => unreachable!("all TypeDefKind variants are covered above"),
}
}
/// Compute the layout for a fixed-size primitive field.
fn compute_fixed_field(
&mut self,
kind: &str,
kind: TypeDefKind,
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
let size = type_size(kind).ok_or_else(|| TypedefError::Offset {
let size = kind.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("type_size returned None for fixed kind {kind}"),
})?;
let natural = natural_alignment(kind);
let natural = kind.natural_alignment();
let align = field_alignment(field_schema, struct_default_align, natural);
align_up(&mut self.offset, align);
let start = self.offset;
@@ -318,11 +320,11 @@ impl<'a> ComputeCtx<'a> {
offset: disc_off,
disc_type,
} => {
let disc_size = type_size(&disc_type).ok_or_else(|| TypedefError::Offset {
let disc_size = disc_type.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("discriminator type {disc_type} has no fixed size"),
})?;
let disc_natural = natural_alignment(&disc_type);
let disc_natural = disc_type.natural_alignment();
let disc_align = union_default_align.max(disc_natural);
align_up(&mut self.offset, disc_align);
let union_start = self.offset;
@@ -378,11 +380,13 @@ impl<'a> ComputeCtx<'a> {
reason: "could not resolve TUnion variant schema ($ref not found)".to_string(),
}
})?;
let v_kind = get_typedef_kind(resolved).ok_or_else(|| TypedefError::Offset {
let v_kind = get_typedef_kind(resolved)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TUnion variant schema has no TypeDef:* kind".to_string(),
})?;
if v_kind != "TypeDef:Struct" {
if v_kind != TypeDefKind::Struct {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("TUnion variant must be TypeDef:Struct, got {v_kind}"),
@@ -416,11 +420,13 @@ impl<'a> ComputeCtx<'a> {
let Some(resolved) = resolve_ref_or_inline(variant_schema, self.root) else {
continue;
};
let v_kind = get_typedef_kind(resolved).ok_or_else(|| TypedefError::Offset {
let v_kind = get_typedef_kind(resolved)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TUnion variant schema has no TypeDef:* kind".to_string(),
})?;
if v_kind != "TypeDef:Struct" {
if v_kind != TypeDefKind::Struct {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("TUnion variant must be TypeDef:Struct, got {v_kind}"),
@@ -468,11 +474,13 @@ impl<'a> ComputeCtx<'a> {
field_path: field_path.to_string(),
reason: "could not resolve TArray items schema".to_string(),
})?;
let elem_kind = get_typedef_kind(element_schema).ok_or_else(|| TypedefError::Offset {
let elem_kind = get_typedef_kind(element_schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: "TArray element schema has no TypeDef:* kind".to_string(),
})?;
if !is_fixed_size(elem_kind) {
if !elem_kind.is_fixed_size() {
return Err(TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!(
@@ -481,11 +489,11 @@ impl<'a> ComputeCtx<'a> {
});
}
let elem_size = type_size(elem_kind).ok_or_else(|| TypedefError::Offset {
let elem_size = elem_kind.type_size().ok_or_else(|| TypedefError::Offset {
field_path: field_path.to_string(),
reason: format!("element kind {elem_kind} has no fixed size"),
})?;
let elem_natural = natural_alignment(elem_kind);
let elem_natural = elem_kind.natural_alignment();
let elem_align = field_alignment(element_schema, struct_default_align, elem_natural);
let stride = round_up(elem_size, elem_align);
+271 -118
View File
@@ -10,13 +10,202 @@
use crate::error::TypedefError;
use serde_json::Value;
use std::fmt;
use std::str::FromStr;
const TYPEDEF_PREFIX: &str = "TypeDef:";
pub(crate) const U32_SIZE: usize = 4;
pub(crate) const DISCRIMINATOR_PATH: &str = "__discriminator";
const BYTE_DISCRIMINATOR_TYPES: &[&str] = &["TypeDef:Uint8", "TypeDef:Uint16", "TypeDef:Uint32"];
const BYTE_DISCRIMINATOR_TYPES: &[TypeDefKind] = &[
TypeDefKind::Uint8,
TypeDefKind::Uint16,
TypeDefKind::Uint32,
];
/// The 17 `TypeDef:*` kinds recognized by the engine.
///
/// Each variant corresponds to a `TypeDef:<name>` JSON Schema keyword.
/// The enum provides compile-time exhaustiveness checking and integer
/// discriminant dispatch (jump table) instead of string comparison.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeDefKind {
Int8,
Int16,
Int32,
Uint8,
Uint16,
Uint32,
Uint64,
Float32,
Float64,
Boolean,
Enum,
String,
Bytes,
Struct,
Union,
Array,
Record,
Timestamp,
}
impl TypeDefKind {
/// The JSON Schema keyword string, e.g. `"TypeDef:Int8"`.
pub fn as_str(self) -> &'static str {
match self {
TypeDefKind::Int8 => "TypeDef:Int8",
TypeDefKind::Int16 => "TypeDef:Int16",
TypeDefKind::Int32 => "TypeDef:Int32",
TypeDefKind::Uint8 => "TypeDef:Uint8",
TypeDefKind::Uint16 => "TypeDef:Uint16",
TypeDefKind::Uint32 => "TypeDef:Uint32",
TypeDefKind::Uint64 => "TypeDef:Uint64",
TypeDefKind::Float32 => "TypeDef:Float32",
TypeDefKind::Float64 => "TypeDef:Float64",
TypeDefKind::Boolean => "TypeDef:Boolean",
TypeDefKind::Enum => "TypeDef:Enum",
TypeDefKind::String => "TypeDef:String",
TypeDefKind::Bytes => "TypeDef:Bytes",
TypeDefKind::Struct => "TypeDef:Struct",
TypeDefKind::Union => "TypeDef:Union",
TypeDefKind::Array => "TypeDef:Array",
TypeDefKind::Record => "TypeDef:Record",
TypeDefKind::Timestamp => "TypeDef:Timestamp",
}
}
/// Fixed byte size, or `None` for variable-size / composite kinds.
pub fn type_size(self) -> Option<usize> {
match self {
TypeDefKind::Float32 | TypeDefKind::Int32 | TypeDefKind::Uint32 | TypeDefKind::Enum => {
Some(4)
}
TypeDefKind::Float64 => Some(8),
TypeDefKind::Int8 | TypeDefKind::Uint8 | TypeDefKind::Boolean => Some(1),
TypeDefKind::Int16 | TypeDefKind::Uint16 => Some(2),
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Struct
| TypeDefKind::Union
| TypeDefKind::Array
| TypeDefKind::Record
| TypeDefKind::Timestamp
| TypeDefKind::Uint64 => None,
}
}
/// Natural alignment: 1 for u8/i8/bool, 2 for u16/i16, 4 for u32/i32/f32/enum,
/// 8 for f64, 4 for variable-length (u32 length prefix), 1 for composites.
pub fn natural_alignment(self) -> usize {
match self {
TypeDefKind::Int8 | TypeDefKind::Uint8 | TypeDefKind::Boolean => 1,
TypeDefKind::Int16 | TypeDefKind::Uint16 => 2,
TypeDefKind::Int32
| TypeDefKind::Uint32
| TypeDefKind::Float32
| TypeDefKind::Enum => 4,
TypeDefKind::Float64 => 8,
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Record
| TypeDefKind::Timestamp => 4,
TypeDefKind::Struct | TypeDefKind::Union | TypeDefKind::Array => 1,
TypeDefKind::Uint64 => 8,
}
}
/// Returns `true` for fixed-size primitive kinds.
pub fn is_fixed_size(self) -> bool {
matches!(
self,
TypeDefKind::Float32
| TypeDefKind::Float64
| TypeDefKind::Int8
| TypeDefKind::Int16
| TypeDefKind::Int32
| TypeDefKind::Uint8
| TypeDefKind::Uint16
| TypeDefKind::Uint32
| TypeDefKind::Boolean
| TypeDefKind::Enum
)
}
/// Returns `true` for kinds whose read/write functions need an `Endian` parameter.
pub fn needs_endian(self) -> bool {
matches!(
self,
TypeDefKind::Int16
| TypeDefKind::Int32
| TypeDefKind::Uint16
| TypeDefKind::Uint32
| TypeDefKind::Uint64
| TypeDefKind::Float32
| TypeDefKind::Float64
| TypeDefKind::Enum
| TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Timestamp
)
}
/// Returns `true` for composite kinds (Struct, Union, Array, Record).
pub fn is_composite(self) -> bool {
matches!(
self,
TypeDefKind::Struct | TypeDefKind::Union | TypeDefKind::Array | TypeDefKind::Record
)
}
/// Returns `true` for variable-length kinds (String, Bytes, Timestamp, Record).
pub fn is_variable_length(self) -> bool {
matches!(
self,
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Timestamp
| TypeDefKind::Record
)
}
}
impl fmt::Display for TypeDefKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TypeDefKind {
type Err = TypedefError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"TypeDef:Int8" => Ok(TypeDefKind::Int8),
"TypeDef:Int16" => Ok(TypeDefKind::Int16),
"TypeDef:Int32" => Ok(TypeDefKind::Int32),
"TypeDef:Uint8" => Ok(TypeDefKind::Uint8),
"TypeDef:Uint16" => Ok(TypeDefKind::Uint16),
"TypeDef:Uint32" => Ok(TypeDefKind::Uint32),
"TypeDef:Uint64" => Ok(TypeDefKind::Uint64),
"TypeDef:Float32" => Ok(TypeDefKind::Float32),
"TypeDef:Float64" => Ok(TypeDefKind::Float64),
"TypeDef:Boolean" => Ok(TypeDefKind::Boolean),
"TypeDef:Enum" => Ok(TypeDefKind::Enum),
"TypeDef:String" => Ok(TypeDefKind::String),
"TypeDef:Bytes" => Ok(TypeDefKind::Bytes),
"TypeDef:Struct" => Ok(TypeDefKind::Struct),
"TypeDef:Union" => Ok(TypeDefKind::Union),
"TypeDef:Array" => Ok(TypeDefKind::Array),
"TypeDef:Record" => Ok(TypeDefKind::Record),
"TypeDef:Timestamp" => Ok(TypeDefKind::Timestamp),
other => Err(TypedefError::Schema(format!(
"unknown TypeDef kind: {other}"
))),
}
}
}
/// Returns the `TypeDef:*` kind string if the schema node declares one.
/// Returns `None` if the node has no `TypeDef:*` keyword.
@@ -35,58 +224,10 @@ pub fn get_typedef_kind(node: &Value) -> Option<&str> {
None
}
/// Returns the fixed byte size for a TypeDef kind, or `None` if variable-size
/// or composite (Struct/Union/Array/Record).
///
/// Only the 17 alknet-typedef kinds are recognized. `TypeDef:Int64` and
/// `TypeDef:Uint64` are NOT among the 17 kinds (TypeBox defines them but
/// alknet-typedef omits them), so they return `None` here.
pub fn type_size(kind: &str) -> Option<usize> {
match kind {
"TypeDef:Float32" | "TypeDef:Int32" | "TypeDef:Uint32" | "TypeDef:Enum" => Some(4),
"TypeDef:Float64" => Some(8),
"TypeDef:Int8" | "TypeDef:Uint8" | "TypeDef:Boolean" => Some(1),
"TypeDef:Int16" | "TypeDef:Uint16" => Some(2),
"TypeDef:String" | "TypeDef:Bytes" | "TypeDef:Struct" | "TypeDef:Union"
| "TypeDef:Array" | "TypeDef:Record" | "TypeDef:Timestamp" => None,
_ => None,
}
}
/// Returns the natural alignment for a TypeDef kind.
///
/// Default alignment: 1 for u8/i8/bool, 2 for u16/i16, 4 for u32/i32/f32/enum,
/// 8 for u64/i64/f64. Variable-length types (String/Bytes/Record/Timestamp)
/// align to 4 (the length prefix is u32). Composite types (Struct/Union/Array)
/// return 1 here — their alignment is computed from their fields during
/// offset computation.
pub fn natural_alignment(kind: &str) -> usize {
match kind {
"TypeDef:Int8" | "TypeDef:Uint8" | "TypeDef:Boolean" => 1,
"TypeDef:Int16" | "TypeDef:Uint16" => 2,
"TypeDef:Int32" | "TypeDef:Uint32" | "TypeDef:Float32" | "TypeDef:Enum" => 4,
"TypeDef:Float64" => 8,
"TypeDef:String" | "TypeDef:Bytes" | "TypeDef:Record" | "TypeDef:Timestamp" => 4,
"TypeDef:Struct" | "TypeDef:Union" | "TypeDef:Array" => 1,
_ => 1,
}
}
/// Returns true if the kind is a fixed-size type (known byte size at schema time).
pub fn is_fixed_size(kind: &str) -> bool {
matches!(
kind,
"TypeDef:Float32"
| "TypeDef:Float64"
| "TypeDef:Int8"
| "TypeDef:Int16"
| "TypeDef:Int32"
| "TypeDef:Uint8"
| "TypeDef:Uint16"
| "TypeDef:Uint32"
| "TypeDef:Boolean"
| "TypeDef:Enum"
)
/// Returns the `TypeDefKind` enum variant if the schema node declares one
/// (boolean form only, like `get_typedef_kind`).
pub fn get_typedef_kind_enum(node: &Value) -> Option<TypeDefKind> {
get_typedef_kind(node).and_then(|s| s.parse().ok())
}
/// Byte endianness for multi-byte integer and float fields.
@@ -172,7 +313,7 @@ pub enum DiscriminatorKind {
offset: usize,
/// The `TypeDef:*` kind of the discriminator (typically
/// `TypeDef:Uint8`).
disc_type: String,
disc_type: TypeDefKind,
},
/// Field-name discriminator: a named field within the struct. Mapping keys
/// are string values matching the discriminator field's value. The
@@ -204,17 +345,24 @@ pub fn parse_discriminator(node: &Value) -> Result<DiscriminatorKind, TypedefErr
match kind {
"byte" => {
let offset = disc_obj.get("offset").and_then(Value::as_u64).unwrap_or(0) as usize;
let disc_type = disc_obj
let disc_type_str = disc_obj
.get("type")
.and_then(Value::as_str)
.unwrap_or("TypeDef:Uint8")
.to_string();
if !BYTE_DISCRIMINATOR_TYPES.contains(&disc_type.as_str()) {
.unwrap_or("TypeDef:Uint8");
let disc_type: TypeDefKind = disc_type_str.parse().map_err(|_| {
TypedefError::Schema(format!(
"discriminator 'type' must be one of {BYTE_DISCRIMINATOR_TYPES:?}, got {disc_type_str:?}"
))
})?;
if !BYTE_DISCRIMINATOR_TYPES.contains(&disc_type) {
return Err(TypedefError::Schema(format!(
"discriminator 'type' must be one of {BYTE_DISCRIMINATOR_TYPES:?}, got {disc_type:?}"
)));
}
Ok(DiscriminatorKind::Byte { offset, disc_type })
Ok(DiscriminatorKind::Byte {
offset,
disc_type,
})
}
"field" => {
let name = disc_obj
@@ -251,6 +399,11 @@ pub fn get_typedef_kind_loose(node: &Value) -> Option<&str> {
None
}
/// Like [`get_typedef_kind_loose`] but returns the parsed [`TypeDefKind`] enum.
pub fn get_typedef_kind_loose_enum(node: &Value) -> Option<TypeDefKind> {
get_typedef_kind_loose(node).and_then(|s| s.parse().ok())
}
/// Resolve a `$ref` against the root schema, or return the inline schema.
///
/// If `node` has a `"$ref"` key, parse the JSON Pointer and walk `root`.
@@ -334,87 +487,87 @@ mod tests {
#[test]
fn type_size_fixed_kinds() {
assert_eq!(type_size("TypeDef:Float32"), Some(4));
assert_eq!(type_size("TypeDef:Float64"), Some(8));
assert_eq!(type_size("TypeDef:Int8"), Some(1));
assert_eq!(type_size("TypeDef:Int16"), Some(2));
assert_eq!(type_size("TypeDef:Int32"), Some(4));
assert_eq!(type_size("TypeDef:Uint8"), Some(1));
assert_eq!(type_size("TypeDef:Uint16"), Some(2));
assert_eq!(type_size("TypeDef:Uint32"), Some(4));
assert_eq!(type_size("TypeDef:Boolean"), Some(1));
assert_eq!(type_size("TypeDef:Enum"), Some(4));
assert_eq!(TypeDefKind::Float32.type_size(), Some(4));
assert_eq!(TypeDefKind::Float64.type_size(), Some(8));
assert_eq!(TypeDefKind::Int8.type_size(), Some(1));
assert_eq!(TypeDefKind::Int16.type_size(), Some(2));
assert_eq!(TypeDefKind::Int32.type_size(), Some(4));
assert_eq!(TypeDefKind::Uint8.type_size(), Some(1));
assert_eq!(TypeDefKind::Uint16.type_size(), Some(2));
assert_eq!(TypeDefKind::Uint32.type_size(), Some(4));
assert_eq!(TypeDefKind::Boolean.type_size(), Some(1));
assert_eq!(TypeDefKind::Enum.type_size(), Some(4));
}
#[test]
fn type_size_variable_and_composite_kinds() {
for kind in [
"TypeDef:String",
"TypeDef:Bytes",
"TypeDef:Struct",
"TypeDef:Union",
"TypeDef:Array",
"TypeDef:Record",
"TypeDef:Timestamp",
TypeDefKind::String,
TypeDefKind::Bytes,
TypeDefKind::Struct,
TypeDefKind::Union,
TypeDefKind::Array,
TypeDefKind::Record,
TypeDefKind::Timestamp,
] {
assert_eq!(type_size(kind), None, "failed for {kind}");
assert_eq!(kind.type_size(), None, "failed for {kind}");
}
}
#[test]
fn type_size_unknown_kind_returns_none() {
assert_eq!(type_size("TypeDef:Int64"), None);
assert_eq!(type_size("TypeDef:Uint64"), None);
assert_eq!(type_size("not-a-typedef"), None);
assert!("TypeDef:Int64".parse::<TypeDefKind>().is_err());
assert!("TypeDef:Uint128".parse::<TypeDefKind>().is_err());
assert!("not-a-typedef".parse::<TypeDefKind>().is_err());
}
#[test]
fn natural_alignment_matches_spec() {
assert_eq!(natural_alignment("TypeDef:Int8"), 1);
assert_eq!(natural_alignment("TypeDef:Uint8"), 1);
assert_eq!(natural_alignment("TypeDef:Boolean"), 1);
assert_eq!(natural_alignment("TypeDef:Int16"), 2);
assert_eq!(natural_alignment("TypeDef:Uint16"), 2);
assert_eq!(natural_alignment("TypeDef:Int32"), 4);
assert_eq!(natural_alignment("TypeDef:Uint32"), 4);
assert_eq!(natural_alignment("TypeDef:Float32"), 4);
assert_eq!(natural_alignment("TypeDef:Enum"), 4);
assert_eq!(natural_alignment("TypeDef:Float64"), 8);
assert_eq!(natural_alignment("TypeDef:String"), 4);
assert_eq!(natural_alignment("TypeDef:Bytes"), 4);
assert_eq!(natural_alignment("TypeDef:Record"), 4);
assert_eq!(natural_alignment("TypeDef:Timestamp"), 4);
assert_eq!(natural_alignment("TypeDef:Struct"), 1);
assert_eq!(natural_alignment("TypeDef:Union"), 1);
assert_eq!(natural_alignment("TypeDef:Array"), 1);
assert_eq!(TypeDefKind::Int8.natural_alignment(), 1);
assert_eq!(TypeDefKind::Uint8.natural_alignment(), 1);
assert_eq!(TypeDefKind::Boolean.natural_alignment(), 1);
assert_eq!(TypeDefKind::Int16.natural_alignment(), 2);
assert_eq!(TypeDefKind::Uint16.natural_alignment(), 2);
assert_eq!(TypeDefKind::Int32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Uint32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Float32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Enum.natural_alignment(), 4);
assert_eq!(TypeDefKind::Float64.natural_alignment(), 8);
assert_eq!(TypeDefKind::String.natural_alignment(), 4);
assert_eq!(TypeDefKind::Bytes.natural_alignment(), 4);
assert_eq!(TypeDefKind::Record.natural_alignment(), 4);
assert_eq!(TypeDefKind::Timestamp.natural_alignment(), 4);
assert_eq!(TypeDefKind::Struct.natural_alignment(), 1);
assert_eq!(TypeDefKind::Union.natural_alignment(), 1);
assert_eq!(TypeDefKind::Array.natural_alignment(), 1);
}
#[test]
fn is_fixed_size_classifies_correctly() {
for kind in [
"TypeDef:Float32",
"TypeDef:Float64",
"TypeDef:Int8",
"TypeDef:Int16",
"TypeDef:Int32",
"TypeDef:Uint8",
"TypeDef:Uint16",
"TypeDef:Uint32",
"TypeDef:Boolean",
"TypeDef:Enum",
TypeDefKind::Float32,
TypeDefKind::Float64,
TypeDefKind::Int8,
TypeDefKind::Int16,
TypeDefKind::Int32,
TypeDefKind::Uint8,
TypeDefKind::Uint16,
TypeDefKind::Uint32,
TypeDefKind::Boolean,
TypeDefKind::Enum,
] {
assert!(is_fixed_size(kind), "expected fixed: {kind}");
assert!(kind.is_fixed_size(), "expected fixed: {kind}");
}
for kind in [
"TypeDef:String",
"TypeDef:Bytes",
"TypeDef:Struct",
"TypeDef:Union",
"TypeDef:Array",
"TypeDef:Record",
"TypeDef:Timestamp",
TypeDefKind::String,
TypeDefKind::Bytes,
TypeDefKind::Struct,
TypeDefKind::Union,
TypeDefKind::Array,
TypeDefKind::Record,
TypeDefKind::Timestamp,
] {
assert!(!is_fixed_size(kind), "expected variable: {kind}");
assert!(!kind.is_fixed_size(), "expected variable: {kind}");
}
}
@@ -510,7 +663,7 @@ mod tests {
disc,
DiscriminatorKind::Byte {
offset: 0,
disc_type: "TypeDef:Uint8".to_string(),
disc_type: TypeDefKind::Uint8,
}
);
}
@@ -525,7 +678,7 @@ mod tests {
disc,
DiscriminatorKind::Byte {
offset: 4,
disc_type: "TypeDef:Uint16".to_string(),
disc_type: TypeDefKind::Uint16,
}
);
}
+36 -36
View File
@@ -12,7 +12,7 @@
use crate::data_access;
use crate::error::TypedefError;
use crate::schema::{self, get_typedef_kind_loose, DiscriminatorKind, Endian, U32_SIZE};
use crate::schema::{self, get_typedef_kind_loose_enum, DiscriminatorKind, Endian, TypeDefKind, U32_SIZE};
use serde_json::Value;
/// A value read from a field during sequential traversal.
@@ -115,8 +115,9 @@ impl SequentialReader {
/// does not declare `TypeDef:Struct`, or has no `properties` object.
pub fn new(schema: &Value) -> Result<Self, TypedefError> {
let kind = schema::get_typedef_kind(schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| TypedefError::Schema("schema has no TypeDef:* kind".to_string()))?;
if kind != "TypeDef:Struct" {
if kind != TypeDefKind::Struct {
return Err(TypedefError::Schema(format!(
"SequentialReader only supports TypeDef:Struct at the top level, got {kind}"
)));
@@ -272,73 +273,73 @@ fn read_field_value<'a>(
offset: usize,
endian: Endian,
) -> Result<(FieldValue<'a>, usize), TypedefError> {
let kind = get_typedef_kind_loose(field_schema).ok_or_else(|| {
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| {
TypedefError::Schema(format!(
"field {field_path} has no TypeDef:* kind: {field_schema}"
))
})?;
match kind {
"TypeDef:Int8" => {
TypeDefKind::Int8 => {
let v = data_access::read_i8(buffer, offset, field_path)?;
Ok((FieldValue::I8(v), offset + 1))
}
"TypeDef:Int16" => {
TypeDefKind::Int16 => {
let v = data_access::read_i16(buffer, offset, field_path, endian)?;
Ok((FieldValue::I16(v), offset + 2))
}
"TypeDef:Int32" => {
TypeDefKind::Int32 => {
let v = data_access::read_i32(buffer, offset, field_path, endian)?;
Ok((FieldValue::I32(v), offset + 4))
}
"TypeDef:Uint8" => {
TypeDefKind::Uint8 => {
let v = data_access::read_u8(buffer, offset, field_path)?;
Ok((FieldValue::U8(v), offset + 1))
}
"TypeDef:Uint16" => {
TypeDefKind::Uint16 => {
let v = data_access::read_u16(buffer, offset, field_path, endian)?;
Ok((FieldValue::U16(v), offset + 2))
}
"TypeDef:Uint32" => {
TypeDefKind::Uint32 => {
let v = data_access::read_u32(buffer, offset, field_path, endian)?;
Ok((FieldValue::U32(v), offset + 4))
}
"TypeDef:Uint64" => {
TypeDefKind::Uint64 => {
let v = data_access::read_u64(buffer, offset, field_path, endian)?;
Ok((FieldValue::U64(v), offset + 8))
}
"TypeDef:Float32" => {
TypeDefKind::Float32 => {
let v = data_access::read_f32(buffer, offset, field_path, endian)?;
Ok((FieldValue::F32(v), offset + 4))
}
"TypeDef:Float64" => {
TypeDefKind::Float64 => {
let v = data_access::read_f64(buffer, offset, field_path, endian)?;
Ok((FieldValue::F64(v), offset + 8))
}
"TypeDef:Boolean" => {
TypeDefKind::Boolean => {
let v = data_access::read_bool(buffer, offset, field_path)?;
Ok((FieldValue::Bool(v), offset + 1))
}
"TypeDef:Enum" => {
TypeDefKind::Enum => {
let v = data_access::read_enum(buffer, offset, field_path, endian)?;
Ok((FieldValue::Enum(v), offset + 4))
}
"TypeDef:String" => {
TypeDefKind::String => {
let s = data_access::read_string(buffer, offset, field_path, endian)?;
let total = U32_SIZE + s.len();
Ok((FieldValue::String(s), offset + total))
}
"TypeDef:Bytes" => {
TypeDefKind::Bytes => {
let b = data_access::read_bytes(buffer, offset, field_path, endian)?;
let total = U32_SIZE + b.len();
Ok((FieldValue::Bytes(b), offset + total))
}
"TypeDef:Timestamp" => {
TypeDefKind::Timestamp => {
let s = data_access::read_string(buffer, offset, field_path, endian)?;
let total = U32_SIZE + s.len();
Ok((FieldValue::String(s), offset + total))
}
"TypeDef:Struct" => {
TypeDefKind::Struct => {
let size = walk_struct_size(root_schema, field_schema, buffer, offset, endian)?;
let end = offset
.checked_add(size)
@@ -348,7 +349,7 @@ fn read_field_value<'a>(
})?;
Ok((FieldValue::Struct { start: offset, end }, end))
}
"TypeDef:Union" => read_union_value(
TypeDefKind::Union => read_union_value(
buffer,
root_schema,
field_schema,
@@ -356,7 +357,7 @@ fn read_field_value<'a>(
offset,
endian,
),
"TypeDef:Array" => read_array_value(
TypeDefKind::Array => read_array_value(
buffer,
root_schema,
field_schema,
@@ -364,7 +365,7 @@ fn read_field_value<'a>(
offset,
endian,
),
"TypeDef:Record" => read_record_value(
TypeDefKind::Record => read_record_value(
buffer,
root_schema,
field_schema,
@@ -372,9 +373,6 @@ fn read_field_value<'a>(
offset,
endian,
),
other => Err(TypedefError::Schema(format!(
"unsupported TypeDef kind for sequential read: {other}"
))),
}
}
@@ -421,7 +419,7 @@ fn read_union_value<'a>(
),
})?;
let (disc_value, disc_size) =
read_byte_discriminator(buffer, abs_offset, field_path, &disc_type, endian)?;
read_byte_discriminator(buffer, abs_offset, field_path, disc_type, endian)?;
let key = disc_value.to_string();
let variant_schema = mapping.get(&key).ok_or_else(|| TypedefError::Access {
field_path: field_path.to_string(),
@@ -514,19 +512,19 @@ fn read_byte_discriminator(
buffer: &[u8],
offset: usize,
field_path: &str,
disc_type: &str,
disc_type: TypeDefKind,
endian: Endian,
) -> Result<(u32, usize), TypedefError> {
match disc_type {
"TypeDef:Uint8" => {
TypeDefKind::Uint8 => {
let v = data_access::read_u8(buffer, offset, field_path)?;
Ok((v as u32, 1))
}
"TypeDef:Uint16" => {
TypeDefKind::Uint16 => {
let v = data_access::read_u16(buffer, offset, field_path, endian)?;
Ok((v as u32, 2))
}
"TypeDef:Uint32" => {
TypeDefKind::Uint32 => {
let v = data_access::read_u32(buffer, offset, field_path, endian)?;
Ok((v, 4))
}
@@ -596,14 +594,14 @@ fn read_array_value<'a>(
(count, offset + U32_SIZE)
};
let element_kind = get_typedef_kind_loose(items_schema).ok_or_else(|| {
let element_kind = get_typedef_kind_loose_enum(items_schema).ok_or_else(|| {
TypedefError::Schema(format!(
"array {field_path} items schema has no TypeDef:* kind"
))
})?;
let element_stride = if schema::is_fixed_size(element_kind) {
schema::type_size(element_kind).unwrap_or(0)
let element_stride = if element_kind.is_fixed_size() {
element_kind.type_size().unwrap_or(0)
} else {
0
};
@@ -733,14 +731,16 @@ fn resolve_and_walk_variant(
"union {field_path} variant could not be resolved: {variant_schema}"
))
})?;
let kind = get_typedef_kind_loose(resolved).ok_or_else(|| {
let kind = get_typedef_kind_loose_enum(resolved).ok_or_else(|| {
TypedefError::Schema(format!(
"union {field_path} variant has no TypeDef:* kind: {resolved}"
))
})?;
match kind {
"TypeDef:Struct" => walk_struct_size(root_schema, resolved, buffer, variant_start, endian),
"TypeDef:Union" => {
TypeDefKind::Struct => {
walk_struct_size(root_schema, resolved, buffer, variant_start, endian)
}
TypeDefKind::Union => {
let (_, end) = read_union_value(
buffer,
root_schema,
@@ -785,7 +785,7 @@ fn resolve_variant_schema<'a>(
}
return None;
}
if get_typedef_kind_loose(variant).is_some() {
if get_typedef_kind_loose_enum(variant).is_some() {
Some(variant)
} else {
None
+15 -13
View File
@@ -11,7 +11,7 @@
use crate::data_access::{read_enum, read_string, read_u16, read_u32, read_u8};
use crate::error::TypedefError;
use crate::schema::{get_typedef_kind, parse_discriminator, DiscriminatorKind, Endian, DISCRIMINATOR_PATH, U32_SIZE};
use crate::schema::{get_typedef_kind, parse_discriminator, DiscriminatorKind, Endian, TypeDefKind, DISCRIMINATOR_PATH, U32_SIZE};
use serde_json::Value;
const STRING_PREFIX_SIZE: usize = 4;
@@ -60,13 +60,13 @@ pub fn read_byte_discriminator(
}
};
let (disc_value, discriminator_size) = match disc_type.as_str() {
"TypeDef:Uint8" => (u32::from(read_u8(buffer, offset, DISCRIMINATOR_PATH)?), 1),
"TypeDef:Uint16" => (
let (disc_value, discriminator_size) = match disc_type {
TypeDefKind::Uint8 => (u32::from(read_u8(buffer, offset, DISCRIMINATOR_PATH)?), 1),
TypeDefKind::Uint16 => (
u32::from(read_u16(buffer, offset, DISCRIMINATOR_PATH, endian)?),
2,
),
"TypeDef:Uint32" => (read_u32(buffer, offset, DISCRIMINATOR_PATH, endian)?, 4),
TypeDefKind::Uint32 => (read_u32(buffer, offset, DISCRIMINATOR_PATH, endian)?, 4),
other => {
return Err(TypedefError::Schema(format!(
"unsupported byte discriminator type: {other}"
@@ -139,14 +139,16 @@ pub fn read_field_discriminator(
))
})?;
let kind = get_typedef_kind(field_schema).ok_or_else(|| {
let kind = get_typedef_kind(field_schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| {
TypedefError::Schema(format!(
"discriminator field '{name}' has no TypeDef:* kind"
))
})?;
let (key, discriminator_field_size) = match kind {
"TypeDef:String" => {
TypeDefKind::String => {
let s = read_string(buffer, disc_field_offset, &name, endian)?;
let size =
STRING_PREFIX_SIZE
@@ -160,11 +162,11 @@ pub fn read_field_discriminator(
})?;
(s.to_string(), size)
}
"TypeDef:Uint8" => {
TypeDefKind::Uint8 => {
let v = read_u8(buffer, disc_field_offset, &name)?;
(v.to_string(), 1)
}
"TypeDef:Enum" => {
TypeDefKind::Enum => {
let v = read_enum(buffer, disc_field_offset, &name, endian)?;
(v.to_string(), U32_SIZE)
}
@@ -249,10 +251,10 @@ pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Val
pub fn discriminator_size(union_schema: &Value) -> Result<usize, TypedefError> {
let disc = parse_discriminator(union_schema)?;
match disc {
DiscriminatorKind::Byte { disc_type, .. } => match disc_type.as_str() {
"TypeDef:Uint8" => Ok(1),
"TypeDef:Uint16" => Ok(2),
"TypeDef:Uint32" => Ok(4),
DiscriminatorKind::Byte { disc_type, .. } => match disc_type {
TypeDefKind::Uint8 => Ok(1),
TypeDefKind::Uint16 => Ok(2),
TypeDefKind::Uint32 => Ok(4),
other => Err(TypedefError::Schema(format!(
"unsupported byte discriminator type: {other}"
))),