style(typedef): apply cargo fmt across all source and test files
Resolve formatting differences flagged by cargo fmt --check during the review checkpoint. No functional changes.
This commit is contained in:
1 parent
bf868157d3
commit
0d39b7f12a
14 files changed
+744
-422
No files matched your search
@@ -60,12 +60,18 @@ fn read_array<const N: usize>(
|
|||||||
let slice = buffer.get(offset..end).ok_or_else(|| {
|
let slice = buffer.get(offset..end).ok_or_else(|| {
|
||||||
access_err(
|
access_err(
|
||||||
field_path,
|
field_path,
|
||||||
format!("slice [{offset}..{end}) unavailable in buffer of length {}", buffer.len()),
|
format!(
|
||||||
|
"slice [{offset}..{end}) unavailable in buffer of length {}",
|
||||||
|
buffer.len()
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
slice
|
slice.try_into().map_err(|_| {
|
||||||
.try_into()
|
access_err(
|
||||||
.map_err(|_| access_err(field_path, format!("internal: try_into failed for {N}-byte slice")))
|
field_path,
|
||||||
|
format!("internal: try_into failed for {N}-byte slice"),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_array<const N: usize>(
|
fn write_array<const N: usize>(
|
||||||
@@ -379,7 +385,12 @@ pub fn write_bool(
|
|||||||
value: bool,
|
value: bool,
|
||||||
field_path: &str,
|
field_path: &str,
|
||||||
) -> Result<(), TypedefError> {
|
) -> Result<(), TypedefError> {
|
||||||
write_array(buffer, offset, [if value { 0x01 } else { 0x00 }], field_path)
|
write_array(
|
||||||
|
buffer,
|
||||||
|
offset,
|
||||||
|
[if value { 0x01 } else { 0x00 }],
|
||||||
|
field_path,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a `TEnum` index (`u32`) `value` at `offset` into `buffer`, applying `endian`.
|
/// Write a `TEnum` index (`u32`) `value` at `offset` into `buffer`, applying `endian`.
|
||||||
@@ -536,12 +547,9 @@ pub fn read_bytes_indirect<'a>(
|
|||||||
field_path: &str,
|
field_path: &str,
|
||||||
endian: Endian,
|
endian: Endian,
|
||||||
) -> Result<&'a [u8], TypedefError> {
|
) -> Result<&'a [u8], TypedefError> {
|
||||||
let struct_end = offset.checked_add(8).ok_or_else(|| {
|
let struct_end = offset
|
||||||
access_err(
|
.checked_add(8)
|
||||||
field_path,
|
.ok_or_else(|| access_err(field_path, format!("offset {offset} + 8 overflows usize")))?;
|
||||||
format!("offset {offset} + 8 overflows usize"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
check_bounds(buffer.len(), offset, struct_end, field_path)?;
|
check_bounds(buffer.len(), offset, struct_end, field_path)?;
|
||||||
let off_bytes: [u8; U32_SIZE] = buffer[offset..offset + U32_SIZE]
|
let off_bytes: [u8; U32_SIZE] = buffer[offset..offset + U32_SIZE]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -639,11 +647,17 @@ mod tests {
|
|||||||
let value: f32 = std::f32::consts::PI;
|
let value: f32 = std::f32::consts::PI;
|
||||||
write_f32(&mut buf, 0, value, "f", LE).unwrap();
|
write_f32(&mut buf, 0, value, "f", LE).unwrap();
|
||||||
let read = read_f32(&buf, 0, "f", LE).unwrap();
|
let read = read_f32(&buf, 0, "f", LE).unwrap();
|
||||||
assert!((read - value).abs() < 1e-6, "le mismatch: {read} vs {value}");
|
assert!(
|
||||||
|
(read - value).abs() < 1e-6,
|
||||||
|
"le mismatch: {read} vs {value}"
|
||||||
|
);
|
||||||
|
|
||||||
write_f32(&mut buf, 0, value, "f", BE).unwrap();
|
write_f32(&mut buf, 0, value, "f", BE).unwrap();
|
||||||
let read = read_f32(&buf, 0, "f", BE).unwrap();
|
let read = read_f32(&buf, 0, "f", BE).unwrap();
|
||||||
assert!((read - value).abs() < 1e-6, "be mismatch: {read} vs {value}");
|
assert!(
|
||||||
|
(read - value).abs() < 1e-6,
|
||||||
|
"be mismatch: {read} vs {value}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -827,4 +841,4 @@ mod tests {
|
|||||||
let bytes = read_bytes(&buf, 0, "data", LE).unwrap();
|
let bytes = read_bytes(&buf, 0, "data", LE).unwrap();
|
||||||
assert!(bytes.is_empty());
|
assert!(bytes.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,9 +44,7 @@ enum Layout {
|
|||||||
},
|
},
|
||||||
/// Aligned static layout. Field offsets are precomputed in an
|
/// Aligned static layout. Field offsets are precomputed in an
|
||||||
/// [`OffsetMap`] for random access.
|
/// [`OffsetMap`] for random access.
|
||||||
Aligned {
|
Aligned { offset_map: OffsetMap },
|
||||||
offset_map: OffsetMap,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The compiled form of a typedef schema. Combines the layout engine
|
/// The compiled form of a typedef schema. Combines the layout engine
|
||||||
@@ -84,10 +82,7 @@ impl TypedefEngine {
|
|||||||
/// underlying layout/validator construction fails. The error is
|
/// underlying layout/validator construction fails. The error is
|
||||||
/// propagated from [`LayoutBuilder::new`], [`SequentialReader::new`],
|
/// propagated from [`LayoutBuilder::new`], [`SequentialReader::new`],
|
||||||
/// [`OffsetMap::compute`], or [`validation::build_validator`].
|
/// [`OffsetMap::compute`], or [`validation::build_validator`].
|
||||||
pub fn compile(
|
pub fn compile(schema: &mut Value, mode: LayoutMode) -> Result<Self, TypedefError> {
|
||||||
schema: &mut Value,
|
|
||||||
mode: LayoutMode,
|
|
||||||
) -> Result<Self, TypedefError> {
|
|
||||||
schema::normalize_refs(schema);
|
schema::normalize_refs(schema);
|
||||||
let endian = Endian::from_schema(schema);
|
let endian = Endian::from_schema(schema);
|
||||||
let layout = match mode {
|
let layout = match mode {
|
||||||
@@ -200,10 +195,12 @@ impl TypedefEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let range = offset_map.get(field_path).ok_or_else(|| TypedefError::Offset {
|
let range = offset_map
|
||||||
field_path: field_path.to_string(),
|
.get(field_path)
|
||||||
reason: "field not found in offset map".to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "field not found in offset map".to_string(),
|
||||||
|
})?;
|
||||||
let field_schema =
|
let field_schema =
|
||||||
lookup_field_schema(&self.schema, field_path).ok_or_else(|| TypedefError::Offset {
|
lookup_field_schema(&self.schema, field_path).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
@@ -275,14 +272,12 @@ impl TypedefEngine {
|
|||||||
start: range.start,
|
start: range.start,
|
||||||
end: range.end,
|
end: range.end,
|
||||||
}),
|
}),
|
||||||
"TypeDef:Union" | "TypeDef:Array" | "TypeDef:Record" => {
|
"TypeDef:Union" | "TypeDef:Array" | "TypeDef:Record" => Err(TypedefError::Access {
|
||||||
Err(TypedefError::Access {
|
field_path: field_path.to_string(),
|
||||||
field_path: field_path.to_string(),
|
reason: "read_field does not support composite types; \
|
||||||
reason: "read_field does not support composite types; \
|
|
||||||
use the layout-specific APIs"
|
use the layout-specific APIs"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
})
|
}),
|
||||||
}
|
|
||||||
other => Err(TypedefError::Access {
|
other => Err(TypedefError::Access {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!("unsupported TypeDef kind for read_field: {other}"),
|
reason: format!("unsupported TypeDef kind for read_field: {other}"),
|
||||||
@@ -324,24 +319,22 @@ impl TypedefEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let range = offset_map.get(field_path).ok_or_else(|| TypedefError::Offset {
|
let range = offset_map
|
||||||
field_path: field_path.to_string(),
|
.get(field_path)
|
||||||
reason: "field not found in offset map".to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "field not found in offset map".to_string(),
|
||||||
|
})?;
|
||||||
let endian = self.endian;
|
let endian = self.endian;
|
||||||
match value {
|
match value {
|
||||||
FieldValue::I8(v) => {
|
FieldValue::I8(v) => data_access::write_i8(buffer, range.start, *v, field_path),
|
||||||
data_access::write_i8(buffer, range.start, *v, field_path)
|
|
||||||
}
|
|
||||||
FieldValue::I16(v) => {
|
FieldValue::I16(v) => {
|
||||||
data_access::write_i16(buffer, range.start, *v, field_path, endian)
|
data_access::write_i16(buffer, range.start, *v, field_path, endian)
|
||||||
}
|
}
|
||||||
FieldValue::I32(v) => {
|
FieldValue::I32(v) => {
|
||||||
data_access::write_i32(buffer, range.start, *v, field_path, endian)
|
data_access::write_i32(buffer, range.start, *v, field_path, endian)
|
||||||
}
|
}
|
||||||
FieldValue::U8(v) => {
|
FieldValue::U8(v) => data_access::write_u8(buffer, range.start, *v, field_path),
|
||||||
data_access::write_u8(buffer, range.start, *v, field_path)
|
|
||||||
}
|
|
||||||
FieldValue::U16(v) => {
|
FieldValue::U16(v) => {
|
||||||
data_access::write_u16(buffer, range.start, *v, field_path, endian)
|
data_access::write_u16(buffer, range.start, *v, field_path, endian)
|
||||||
}
|
}
|
||||||
@@ -357,9 +350,7 @@ impl TypedefEngine {
|
|||||||
FieldValue::F64(v) => {
|
FieldValue::F64(v) => {
|
||||||
data_access::write_f64(buffer, range.start, *v, field_path, endian)
|
data_access::write_f64(buffer, range.start, *v, field_path, endian)
|
||||||
}
|
}
|
||||||
FieldValue::Bool(v) => {
|
FieldValue::Bool(v) => data_access::write_bool(buffer, range.start, *v, field_path),
|
||||||
data_access::write_bool(buffer, range.start, *v, field_path)
|
|
||||||
}
|
|
||||||
FieldValue::Enum(v) => {
|
FieldValue::Enum(v) => {
|
||||||
data_access::write_enum(buffer, range.start, *v, field_path, endian)
|
data_access::write_enum(buffer, range.start, *v, field_path, endian)
|
||||||
}
|
}
|
||||||
@@ -371,14 +362,14 @@ impl TypedefEngine {
|
|||||||
data_access::write_bytes(buffer, range.start, v, field_path, endian)?;
|
data_access::write_bytes(buffer, range.start, v, field_path, endian)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
FieldValue::Struct { .. }
|
FieldValue::Struct { .. } | FieldValue::Union { .. } | FieldValue::Array { .. } => {
|
||||||
| FieldValue::Union { .. }
|
Err(TypedefError::Access {
|
||||||
| FieldValue::Array { .. } => Err(TypedefError::Access {
|
field_path: field_path.to_string(),
|
||||||
field_path: field_path.to_string(),
|
reason: "write_field does not support composite types; \
|
||||||
reason: "write_field does not support composite types; \
|
|
||||||
use the layout-specific APIs"
|
use the layout-specific APIs"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,10 +395,7 @@ impl fmt::Debug for TypedefEngine {
|
|||||||
fn lookup_field_schema<'a>(schema: &'a Value, field_path: &str) -> Option<&'a Value> {
|
fn lookup_field_schema<'a>(schema: &'a Value, field_path: &str) -> Option<&'a Value> {
|
||||||
let mut current = schema;
|
let mut current = schema;
|
||||||
for segment in field_path.split('.') {
|
for segment in field_path.split('.') {
|
||||||
current = current
|
current = current.as_object()?.get("properties")?.get(segment)?;
|
||||||
.as_object()?
|
|
||||||
.get("properties")?
|
|
||||||
.get(segment)?;
|
|
||||||
}
|
}
|
||||||
Some(current)
|
Some(current)
|
||||||
}
|
}
|
||||||
@@ -723,11 +711,7 @@ mod tests {
|
|||||||
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
|
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
|
||||||
let mut buf = [0u8; 8];
|
let mut buf = [0u8; 8];
|
||||||
let err = engine
|
let err = engine
|
||||||
.write_field(
|
.write_field(&mut buf, "id", &FieldValue::Struct { start: 0, end: 4 })
|
||||||
&mut buf,
|
|
||||||
"id",
|
|
||||||
&FieldValue::Struct { start: 0, end: 4 },
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
}
|
}
|
||||||
@@ -772,4 +756,4 @@ mod tests {
|
|||||||
let node = json!({ "TypeDef:String": { "encoding": "offset-indirect" } });
|
let node = json!({ "TypeDef:String": { "encoding": "offset-indirect" } });
|
||||||
assert_eq!(typedef_kind_loose(&node), Some("TypeDef:String"));
|
assert_eq!(typedef_kind_loose(&node), Some("TypeDef:String"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,17 +15,11 @@ pub enum TypedefError {
|
|||||||
|
|
||||||
/// Offset computation errors — field not found, type not supported
|
/// Offset computation errors — field not found, type not supported
|
||||||
/// for offset computation, recursive depth exceeded.
|
/// for offset computation, recursive depth exceeded.
|
||||||
Offset {
|
Offset { field_path: String, reason: String },
|
||||||
field_path: String,
|
|
||||||
reason: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Read/write errors — buffer too short, invalid UTF-8, value out
|
/// Read/write errors — buffer too short, invalid UTF-8, value out
|
||||||
/// of range for the target type.
|
/// of range for the target type.
|
||||||
Access {
|
Access { field_path: String, reason: String },
|
||||||
field_path: String,
|
|
||||||
reason: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Validation errors — delegated to the `jsonschema` crate.
|
/// Validation errors — delegated to the `jsonschema` crate.
|
||||||
/// The `'static` lifetime is correct: the validator owns its schema
|
/// The `'static` lifetime is correct: the validator owns its schema
|
||||||
@@ -48,4 +42,4 @@ impl fmt::Display for TypedefError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for TypedefError {}
|
impl std::error::Error for TypedefError {}
|
||||||
@@ -183,10 +183,7 @@ impl LayoutBuilder {
|
|||||||
/// - [`TypedefError::Offset`] for missing variable-length field
|
/// - [`TypedefError::Offset`] for missing variable-length field
|
||||||
/// sizes in `var_sizes`, missing discriminator values, or unknown
|
/// sizes in `var_sizes`, missing discriminator values, or unknown
|
||||||
/// discriminator values.
|
/// discriminator values.
|
||||||
pub fn build(
|
pub fn build(&self, var_sizes: &HashMap<String, usize>) -> Result<PackedLayout, TypedefError> {
|
||||||
&self,
|
|
||||||
var_sizes: &HashMap<String, usize>,
|
|
||||||
) -> Result<PackedLayout, TypedefError> {
|
|
||||||
let mut ctx = BuildCtx {
|
let mut ctx = BuildCtx {
|
||||||
root: &self.schema,
|
root: &self.schema,
|
||||||
var_sizes,
|
var_sizes,
|
||||||
@@ -298,12 +295,14 @@ impl<'a> BuildCtx<'a> {
|
|||||||
offset: &mut usize,
|
offset: &mut usize,
|
||||||
kind: &str,
|
kind: &str,
|
||||||
) -> Result<(), TypedefError> {
|
) -> Result<(), TypedefError> {
|
||||||
let data_size = self.var_sizes.get(field_path).copied().ok_or_else(|| {
|
let data_size =
|
||||||
TypedefError::Offset {
|
self.var_sizes
|
||||||
field_path: field_path.to_string(),
|
.get(field_path)
|
||||||
reason: "missing variable-length field size".to_string(),
|
.copied()
|
||||||
}
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "missing variable-length field size".to_string(),
|
||||||
|
})?;
|
||||||
let start = *offset;
|
let start = *offset;
|
||||||
let total = U32_SIZE
|
let total = U32_SIZE
|
||||||
.checked_add(data_size)
|
.checked_add(data_size)
|
||||||
@@ -328,21 +327,22 @@ impl<'a> BuildCtx<'a> {
|
|||||||
field_path: &str,
|
field_path: &str,
|
||||||
offset: &mut usize,
|
offset: &mut usize,
|
||||||
) -> Result<(), TypedefError> {
|
) -> Result<(), TypedefError> {
|
||||||
let obj = field_schema.as_object().ok_or_else(|| TypedefError::Offset {
|
let obj = field_schema
|
||||||
field_path: field_path.to_string(),
|
.as_object()
|
||||||
reason: "array schema is not an object".to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "array schema is not an object".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let items = obj.get("items").ok_or_else(|| TypedefError::Offset {
|
let items = obj.get("items").ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "TArray is missing 'items'".to_string(),
|
reason: "TArray is missing 'items'".to_string(),
|
||||||
})?;
|
})?;
|
||||||
let element_schema = resolve_ref_or_inline(items, self.root).ok_or_else(|| {
|
let element_schema =
|
||||||
TypedefError::Offset {
|
resolve_ref_or_inline(items, self.root).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "could not resolve TArray items schema".to_string(),
|
reason: "could not resolve TArray items schema".to_string(),
|
||||||
}
|
})?;
|
||||||
})?;
|
|
||||||
let elem_kind = typedef_kind_loose(element_schema).ok_or_else(|| TypedefError::Offset {
|
let elem_kind = typedef_kind_loose(element_schema).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "TArray element schema has no TypeDef:* kind".to_string(),
|
reason: "TArray element schema has no TypeDef:* kind".to_string(),
|
||||||
@@ -362,8 +362,14 @@ impl<'a> BuildCtx<'a> {
|
|||||||
reason: format!("element kind {elem_kind} has no fixed size"),
|
reason: format!("element kind {elem_kind} has no fixed size"),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let min_items = obj.get("minItems").and_then(Value::as_u64).map(|n| n as usize);
|
let min_items = obj
|
||||||
let max_items = obj.get("maxItems").and_then(Value::as_u64).map(|n| n as usize);
|
.get("minItems")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.map(|n| n as usize);
|
||||||
|
let max_items = obj
|
||||||
|
.get("maxItems")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.map(|n| n as usize);
|
||||||
let fixed_count = match (min_items, max_items) {
|
let fixed_count = match (min_items, max_items) {
|
||||||
(Some(mn), Some(mx)) if mn == mx => Some(mn),
|
(Some(mn), Some(mx)) if mn == mx => Some(mn),
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -373,15 +379,25 @@ impl<'a> BuildCtx<'a> {
|
|||||||
let start = *offset;
|
let start = *offset;
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let elem_offset = start
|
let elem_offset = start
|
||||||
.checked_add(i.checked_mul(elem_size).ok_or_else(|| TypedefError::Offset {
|
.checked_add(
|
||||||
field_path: field_path.to_string(),
|
i.checked_mul(elem_size)
|
||||||
reason: format!("element index {i} × size {elem_size} overflows usize"),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?)
|
field_path: field_path.to_string(),
|
||||||
|
reason: format!(
|
||||||
|
"element index {i} × size {elem_size} overflows usize"
|
||||||
|
),
|
||||||
|
})?,
|
||||||
|
)
|
||||||
.ok_or_else(|| TypedefError::Offset {
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!("element offset {start} + {i}×{elem_size} overflows usize"),
|
reason: format!("element offset {start} + {i}×{elem_size} overflows usize"),
|
||||||
})?;
|
})?;
|
||||||
self.push(&format!("{field_path}[{i}]"), elem_offset, elem_size, elem_kind);
|
self.push(
|
||||||
|
&format!("{field_path}[{i}]"),
|
||||||
|
elem_offset,
|
||||||
|
elem_size,
|
||||||
|
elem_kind,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let array_size = count
|
let array_size = count
|
||||||
.checked_mul(elem_size)
|
.checked_mul(elem_size)
|
||||||
@@ -397,18 +413,22 @@ impl<'a> BuildCtx<'a> {
|
|||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let data_size = self.var_sizes.get(field_path).copied().ok_or_else(|| {
|
let data_size =
|
||||||
TypedefError::Offset {
|
self.var_sizes
|
||||||
field_path: field_path.to_string(),
|
.get(field_path)
|
||||||
reason: "missing variable-count array element data size".to_string(),
|
.copied()
|
||||||
}
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "missing variable-count array element data size".to_string(),
|
||||||
|
})?;
|
||||||
let start = *offset;
|
let start = *offset;
|
||||||
let total = U32_SIZE
|
let total = U32_SIZE
|
||||||
.checked_add(data_size)
|
.checked_add(data_size)
|
||||||
.ok_or_else(|| TypedefError::Offset {
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!("count prefix {U32_SIZE} + data size {data_size} overflows usize"),
|
reason: format!(
|
||||||
|
"count prefix {U32_SIZE} + data size {data_size} overflows usize"
|
||||||
|
),
|
||||||
})?;
|
})?;
|
||||||
*offset = start
|
*offset = start
|
||||||
.checked_add(total)
|
.checked_add(total)
|
||||||
@@ -439,15 +459,11 @@ impl<'a> BuildCtx<'a> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
match disc {
|
match disc {
|
||||||
DiscriminatorKind::Byte { offset: disc_off, disc_type } => {
|
DiscriminatorKind::Byte {
|
||||||
self.walk_byte_discriminator_union(
|
offset: disc_off,
|
||||||
field_path,
|
disc_type,
|
||||||
offset,
|
} => self
|
||||||
&disc_type,
|
.walk_byte_discriminator_union(field_path, offset, &disc_type, disc_off, mapping),
|
||||||
disc_off,
|
|
||||||
mapping,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DiscriminatorKind::Field { name: _ } => {
|
DiscriminatorKind::Field { name: _ } => {
|
||||||
self.walk_field_discriminator_union(field_path, offset, mapping)
|
self.walk_field_discriminator_union(field_path, offset, mapping)
|
||||||
}
|
}
|
||||||
@@ -469,27 +485,34 @@ impl<'a> BuildCtx<'a> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let disc_key = format!("{field_path}.{DISCRIMINATOR_PATH}");
|
let disc_key = format!("{field_path}.{DISCRIMINATOR_PATH}");
|
||||||
let disc_value = self.var_sizes.get(&disc_key).copied().ok_or_else(|| {
|
let disc_value =
|
||||||
TypedefError::Offset {
|
self.var_sizes
|
||||||
field_path: field_path.to_string(),
|
.get(&disc_key)
|
||||||
reason: format!("missing discriminator value at key '{disc_key}'"),
|
.copied()
|
||||||
}
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: format!("missing discriminator value at key '{disc_key}'"),
|
||||||
|
})?;
|
||||||
|
|
||||||
let union_start = *offset;
|
let union_start = *offset;
|
||||||
let disc_abs_offset = union_start
|
let disc_abs_offset =
|
||||||
.checked_add(disc_off)
|
union_start
|
||||||
.ok_or_else(|| TypedefError::Offset {
|
.checked_add(disc_off)
|
||||||
field_path: field_path.to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
reason: format!("union offset {union_start} + disc offset {disc_off} overflows usize"),
|
field_path: field_path.to_string(),
|
||||||
})?;
|
reason: format!(
|
||||||
|
"union offset {union_start} + disc offset {disc_off} overflows usize"
|
||||||
|
),
|
||||||
|
})?;
|
||||||
self.push(&disc_key, disc_abs_offset, disc_size, disc_type);
|
self.push(&disc_key, disc_abs_offset, disc_size, disc_type);
|
||||||
|
|
||||||
let variant_key = disc_value.to_string();
|
let variant_key = disc_value.to_string();
|
||||||
let variant_schema = mapping.get(&variant_key).ok_or_else(|| TypedefError::Offset {
|
let variant_schema = mapping
|
||||||
field_path: field_path.to_string(),
|
.get(&variant_key)
|
||||||
reason: format!("unknown discriminator value: {variant_key}"),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: format!("unknown discriminator value: {variant_key}"),
|
||||||
|
})?;
|
||||||
let resolved = resolve_ref_or_inline(variant_schema, self.root).ok_or_else(|| {
|
let resolved = resolve_ref_or_inline(variant_schema, self.root).ok_or_else(|| {
|
||||||
TypedefError::Offset {
|
TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
@@ -507,14 +530,15 @@ impl<'a> BuildCtx<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let variant_start = disc_abs_offset
|
let variant_start =
|
||||||
.checked_add(disc_size)
|
disc_abs_offset
|
||||||
.ok_or_else(|| TypedefError::Offset {
|
.checked_add(disc_size)
|
||||||
field_path: field_path.to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
reason: format!(
|
field_path: field_path.to_string(),
|
||||||
"variant start {disc_abs_offset} + disc size {disc_size} overflows usize"
|
reason: format!(
|
||||||
),
|
"variant start {disc_abs_offset} + disc size {disc_size} overflows usize"
|
||||||
})?;
|
),
|
||||||
|
})?;
|
||||||
*offset = variant_start;
|
*offset = variant_start;
|
||||||
self.walk_struct(resolved, field_path, offset)?;
|
self.walk_struct(resolved, field_path, offset)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -532,21 +556,25 @@ impl<'a> BuildCtx<'a> {
|
|||||||
mapping: &serde_json::Map<String, Value>,
|
mapping: &serde_json::Map<String, Value>,
|
||||||
) -> Result<(), TypedefError> {
|
) -> Result<(), TypedefError> {
|
||||||
let variant_key = format!("{field_path}.{VARIANT_KEY}");
|
let variant_key = format!("{field_path}.{VARIANT_KEY}");
|
||||||
let variant_index = self.var_sizes.get(&variant_key).copied().ok_or_else(|| {
|
let variant_index =
|
||||||
TypedefError::Offset {
|
self.var_sizes
|
||||||
field_path: field_path.to_string(),
|
.get(&variant_key)
|
||||||
reason: format!("missing variant index at key '{variant_key}'"),
|
.copied()
|
||||||
}
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
let variant_entry = mapping.iter().nth(variant_index).ok_or_else(|| {
|
reason: format!("missing variant index at key '{variant_key}'"),
|
||||||
TypedefError::Offset {
|
})?;
|
||||||
field_path: field_path.to_string(),
|
let variant_entry =
|
||||||
reason: format!(
|
mapping
|
||||||
"variant index {variant_index} out of range (mapping has {} entries)",
|
.iter()
|
||||||
mapping.len()
|
.nth(variant_index)
|
||||||
),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
}
|
field_path: field_path.to_string(),
|
||||||
})?;
|
reason: format!(
|
||||||
|
"variant index {variant_index} out of range (mapping has {} entries)",
|
||||||
|
mapping.len()
|
||||||
|
),
|
||||||
|
})?;
|
||||||
let variant_schema = variant_entry.1;
|
let variant_schema = variant_entry.1;
|
||||||
let resolved = resolve_ref_or_inline(variant_schema, self.root).ok_or_else(|| {
|
let resolved = resolve_ref_or_inline(variant_schema, self.root).ok_or_else(|| {
|
||||||
TypedefError::Offset {
|
TypedefError::Offset {
|
||||||
@@ -675,15 +703,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[]));
|
let layout = build(&schema, &var_sizes(&[]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("a"),
|
layout.get("a"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("b"),
|
layout.get("b"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("c"),
|
layout.get("c"),
|
||||||
Some(&FieldPosition { offset: 5, size: 2, kind: "TypeDef:Uint16".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 2,
|
||||||
|
kind: "TypeDef:Uint16".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 7);
|
assert_eq!(layout.total_size(), 7);
|
||||||
}
|
}
|
||||||
@@ -701,15 +741,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("payload", 10)]));
|
let layout = build(&schema, &var_sizes(&[("payload", 10)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("flag"),
|
layout.get("flag"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("id"),
|
layout.get("id"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload"),
|
layout.get("payload"),
|
||||||
Some(&FieldPosition { offset: 5, size: 4, kind: "TypeDef:String".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:String".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 19);
|
assert_eq!(layout.total_size(), 19);
|
||||||
}
|
}
|
||||||
@@ -726,11 +778,19 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("name", 5)]));
|
let layout = build(&schema, &var_sizes(&[("name", 5)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("name"),
|
layout.get("name"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:String".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:String".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("tail"),
|
layout.get("tail"),
|
||||||
Some(&FieldPosition { offset: 9, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 9,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 10);
|
assert_eq!(layout.total_size(), 10);
|
||||||
}
|
}
|
||||||
@@ -746,7 +806,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("blob", 3)]));
|
let layout = build(&schema, &var_sizes(&[("blob", 3)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("blob"),
|
layout.get("blob"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Bytes".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Bytes".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 7);
|
assert_eq!(layout.total_size(), 7);
|
||||||
}
|
}
|
||||||
@@ -762,7 +826,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("ts", 20)]));
|
let layout = build(&schema, &var_sizes(&[("ts", 20)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("ts"),
|
layout.get("ts"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Timestamp".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Timestamp".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 24);
|
assert_eq!(layout.total_size(), 24);
|
||||||
}
|
}
|
||||||
@@ -781,7 +849,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("counts", 100)]));
|
let layout = build(&schema, &var_sizes(&[("counts", 100)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("counts"),
|
layout.get("counts"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Record".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Record".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 104);
|
assert_eq!(layout.total_size(), 104);
|
||||||
}
|
}
|
||||||
@@ -799,7 +871,10 @@ mod tests {
|
|||||||
match err {
|
match err {
|
||||||
TypedefError::Offset { field_path, reason } => {
|
TypedefError::Offset { field_path, reason } => {
|
||||||
assert_eq!(field_path, "name");
|
assert_eq!(field_path, "name");
|
||||||
assert!(reason.contains("missing variable-length field size"), "reason: {reason}");
|
assert!(
|
||||||
|
reason.contains("missing variable-length field size"),
|
||||||
|
"reason: {reason}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
other => panic!("expected Offset, got {other:?}"),
|
other => panic!("expected Offset, got {other:?}"),
|
||||||
}
|
}
|
||||||
@@ -823,15 +898,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[]));
|
let layout = build(&schema, &var_sizes(&[]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("header.magic"),
|
layout.get("header.magic"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("header.version"),
|
layout.get("header.version"),
|
||||||
Some(&FieldPosition { offset: 4, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 4,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("body"),
|
layout.get("body"),
|
||||||
Some(&FieldPosition { offset: 5, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 9);
|
assert_eq!(layout.total_size(), 9);
|
||||||
}
|
}
|
||||||
@@ -854,15 +941,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("header.name", 3)]));
|
let layout = build(&schema, &var_sizes(&[("header.name", 3)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("header.id"),
|
layout.get("header.id"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("header.name"),
|
layout.get("header.name"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:String".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:String".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("tail"),
|
layout.get("tail"),
|
||||||
Some(&FieldPosition { offset: 8, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 8,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 9);
|
assert_eq!(layout.total_size(), 9);
|
||||||
}
|
}
|
||||||
@@ -883,15 +982,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[]));
|
let layout = build(&schema, &var_sizes(&[]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals[0]"),
|
layout.get("vals[0]"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals[1]"),
|
layout.get("vals[1]"),
|
||||||
Some(&FieldPosition { offset: 4, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 4,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals[2]"),
|
layout.get("vals[2]"),
|
||||||
Some(&FieldPosition { offset: 8, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 8,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 12);
|
assert_eq!(layout.total_size(), 12);
|
||||||
}
|
}
|
||||||
@@ -913,15 +1024,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[]));
|
let layout = build(&schema, &var_sizes(&[]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("id"),
|
layout.get("id"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals[0]"),
|
layout.get("vals[0]"),
|
||||||
Some(&FieldPosition { offset: 1, size: 2, kind: "TypeDef:Uint16".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 2,
|
||||||
|
kind: "TypeDef:Uint16".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals[1]"),
|
layout.get("vals[1]"),
|
||||||
Some(&FieldPosition { offset: 3, size: 2, kind: "TypeDef:Uint16".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 3,
|
||||||
|
size: 2,
|
||||||
|
kind: "TypeDef:Uint16".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 5);
|
assert_eq!(layout.total_size(), 5);
|
||||||
}
|
}
|
||||||
@@ -940,7 +1063,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("vals", 12)]));
|
let layout = build(&schema, &var_sizes(&[("vals", 12)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("vals"),
|
layout.get("vals"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:Array".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Array".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 16);
|
assert_eq!(layout.total_size(), 16);
|
||||||
}
|
}
|
||||||
@@ -1022,15 +1149,27 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.__discriminator"),
|
layout.get("payload.__discriminator"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.handle"),
|
layout.get("payload.handle"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.length"),
|
layout.get("payload.length"),
|
||||||
Some(&FieldPosition { offset: 5, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 9);
|
assert_eq!(layout.total_size(), 9);
|
||||||
}
|
}
|
||||||
@@ -1075,11 +1214,19 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.__discriminator"),
|
layout.get("payload.__discriminator"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.data"),
|
layout.get("payload.data"),
|
||||||
Some(&FieldPosition { offset: 9, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 9,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 13);
|
assert_eq!(layout.total_size(), 13);
|
||||||
}
|
}
|
||||||
@@ -1111,22 +1258,31 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let vs = var_sizes(&[
|
let vs = var_sizes(&[("packet.__discriminator", 5), ("packet.path", 8)]);
|
||||||
("packet.__discriminator", 5),
|
|
||||||
("packet.path", 8),
|
|
||||||
]);
|
|
||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("packet.__discriminator"),
|
layout.get("packet.__discriminator"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("packet.handle"),
|
layout.get("packet.handle"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("packet.path"),
|
layout.get("packet.path"),
|
||||||
Some(&FieldPosition { offset: 5, size: 4, kind: "TypeDef:String".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:String".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 17);
|
assert_eq!(layout.total_size(), 17);
|
||||||
}
|
}
|
||||||
@@ -1231,11 +1387,19 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("event.type"),
|
layout.get("event.type"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("event.handle"),
|
layout.get("event.handle"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 5);
|
assert_eq!(layout.total_size(), 5);
|
||||||
}
|
}
|
||||||
@@ -1276,7 +1440,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("event.length"),
|
layout.get("event.length"),
|
||||||
Some(&FieldPosition { offset: 5, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 5,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 9);
|
assert_eq!(layout.total_size(), 9);
|
||||||
}
|
}
|
||||||
@@ -1450,7 +1618,11 @@ mod tests {
|
|||||||
let layout = build(&schema, &var_sizes(&[("name", 5)]));
|
let layout = build(&schema, &var_sizes(&[("name", 5)]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("name"),
|
layout.get("name"),
|
||||||
Some(&FieldPosition { offset: 0, size: 4, kind: "TypeDef:String".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:String".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 9);
|
assert_eq!(layout.total_size(), 9);
|
||||||
}
|
}
|
||||||
@@ -1524,11 +1696,19 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("packet.__discriminator"),
|
layout.get("packet.__discriminator"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("packet.x"),
|
layout.get("packet.x"),
|
||||||
Some(&FieldPosition { offset: 1, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 5);
|
assert_eq!(layout.total_size(), 5);
|
||||||
}
|
}
|
||||||
@@ -1564,16 +1744,28 @@ mod tests {
|
|||||||
let layout = build(&schema, &vs);
|
let layout = build(&schema, &vs);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("id"),
|
layout.get("id"),
|
||||||
Some(&FieldPosition { offset: 0, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 0,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.__discriminator"),
|
layout.get("payload.__discriminator"),
|
||||||
Some(&FieldPosition { offset: 1, size: 1, kind: "TypeDef:Uint8".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 1,
|
||||||
|
size: 1,
|
||||||
|
kind: "TypeDef:Uint8".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout.get("payload.handle"),
|
layout.get("payload.handle"),
|
||||||
Some(&FieldPosition { offset: 2, size: 4, kind: "TypeDef:Uint32".into() })
|
Some(&FieldPosition {
|
||||||
|
offset: 2,
|
||||||
|
size: 4,
|
||||||
|
kind: "TypeDef:Uint32".into()
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(layout.total_size(), 6);
|
assert_eq!(layout.total_size(), 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,14 +31,14 @@ pub mod sequential_reader;
|
|||||||
pub mod tunion;
|
pub mod tunion;
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
|
|
||||||
|
pub use engine::{LayoutMode, TypedefEngine};
|
||||||
pub use error::TypedefError;
|
pub use error::TypedefError;
|
||||||
pub use schema::{
|
|
||||||
parse_align, parse_discriminator, parse_encoding, parse_endian, parse_max_length,
|
|
||||||
normalize_refs, DiscriminatorKind, Endian, VariableEncoding,
|
|
||||||
};
|
|
||||||
pub use validation::build_validator;
|
|
||||||
pub use offset_map::{ByteRange, OffsetMap};
|
|
||||||
pub use layout_builder::{FieldPosition, LayoutBuilder, PackedLayout};
|
pub use layout_builder::{FieldPosition, LayoutBuilder, PackedLayout};
|
||||||
|
pub use offset_map::{ByteRange, OffsetMap};
|
||||||
|
pub use schema::{
|
||||||
|
normalize_refs, parse_align, parse_discriminator, parse_encoding, parse_endian,
|
||||||
|
parse_max_length, DiscriminatorKind, Endian, VariableEncoding,
|
||||||
|
};
|
||||||
pub use sequential_reader::{FieldValue, SequentialReader};
|
pub use sequential_reader::{FieldValue, SequentialReader};
|
||||||
pub use tunion::UnionDispatch;
|
pub use tunion::UnionDispatch;
|
||||||
pub use engine::{LayoutMode, TypedefEngine};
|
pub use validation::build_validator;
|
||||||
@@ -165,9 +165,9 @@ impl<'a> ComputeCtx<'a> {
|
|||||||
prefix: &str,
|
prefix: &str,
|
||||||
parent_struct_align: usize,
|
parent_struct_align: usize,
|
||||||
) -> Result<(usize, usize), TypedefError> {
|
) -> Result<(usize, usize), TypedefError> {
|
||||||
let obj = struct_schema.as_object().ok_or_else(|| {
|
let obj = struct_schema
|
||||||
TypedefError::Schema("struct schema is not an object".to_string())
|
.as_object()
|
||||||
})?;
|
.ok_or_else(|| TypedefError::Schema("struct schema is not an object".to_string()))?;
|
||||||
let properties = obj
|
let properties = obj
|
||||||
.get("properties")
|
.get("properties")
|
||||||
.and_then(|v| v.as_object())
|
.and_then(|v| v.as_object())
|
||||||
@@ -316,7 +316,10 @@ impl<'a> ComputeCtx<'a> {
|
|||||||
let union_default_align = union_align_annotation.unwrap_or(struct_default_align);
|
let union_default_align = union_align_annotation.unwrap_or(struct_default_align);
|
||||||
|
|
||||||
match disc {
|
match disc {
|
||||||
DiscriminatorKind::Byte { offset: disc_off, disc_type } => {
|
DiscriminatorKind::Byte {
|
||||||
|
offset: disc_off,
|
||||||
|
disc_type,
|
||||||
|
} => {
|
||||||
let disc_size = type_size(&disc_type).ok_or_else(|| TypedefError::Offset {
|
let disc_size = type_size(&disc_type).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!("discriminator type {disc_type} has no fixed size"),
|
reason: format!("discriminator type {disc_type} has no fixed size"),
|
||||||
@@ -451,21 +454,22 @@ impl<'a> ComputeCtx<'a> {
|
|||||||
field_path: &str,
|
field_path: &str,
|
||||||
struct_default_align: usize,
|
struct_default_align: usize,
|
||||||
) -> Result<FieldLayout, TypedefError> {
|
) -> Result<FieldLayout, TypedefError> {
|
||||||
let obj = field_schema.as_object().ok_or_else(|| TypedefError::Offset {
|
let obj = field_schema
|
||||||
field_path: field_path.to_string(),
|
.as_object()
|
||||||
reason: "array schema is not an object".to_string(),
|
.ok_or_else(|| TypedefError::Offset {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: "array schema is not an object".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let items = obj.get("items").ok_or_else(|| TypedefError::Offset {
|
let items = obj.get("items").ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "TArray is missing 'items'".to_string(),
|
reason: "TArray is missing 'items'".to_string(),
|
||||||
})?;
|
})?;
|
||||||
let element_schema = resolve_ref_or_inline(items, self.root).ok_or_else(|| {
|
let element_schema =
|
||||||
TypedefError::Offset {
|
resolve_ref_or_inline(items, self.root).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "could not resolve TArray items schema".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).ok_or_else(|| TypedefError::Offset {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: "TArray element schema has no TypeDef:* kind".to_string(),
|
reason: "TArray element schema has no TypeDef:* kind".to_string(),
|
||||||
@@ -487,8 +491,14 @@ impl<'a> ComputeCtx<'a> {
|
|||||||
let elem_align = field_alignment(element_schema, struct_default_align, elem_natural);
|
let elem_align = field_alignment(element_schema, struct_default_align, elem_natural);
|
||||||
let stride = round_up(elem_size, elem_align);
|
let stride = round_up(elem_size, elem_align);
|
||||||
|
|
||||||
let min_items = obj.get("minItems").and_then(|v| v.as_u64()).map(|n| n as usize);
|
let min_items = obj
|
||||||
let max_items = obj.get("maxItems").and_then(|v| v.as_u64()).map(|n| n as usize);
|
.get("minItems")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.map(|n| n as usize);
|
||||||
|
let max_items = obj
|
||||||
|
.get("maxItems")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.map(|n| n as usize);
|
||||||
let fixed_count = match (min_items, max_items) {
|
let fixed_count = match (min_items, max_items) {
|
||||||
(Some(mn), Some(mx)) if mn == mx => Some(mn),
|
(Some(mn), Some(mx)) if mn == mx => Some(mn),
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -718,7 +728,10 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let m = map(&schema);
|
let m = map(&schema);
|
||||||
assert_eq!(m.get("header.magic"), Some(&ByteRange { start: 0, end: 4 }));
|
assert_eq!(m.get("header.magic"), Some(&ByteRange { start: 0, end: 4 }));
|
||||||
assert_eq!(m.get("header.version"), Some(&ByteRange { start: 4, end: 5 }));
|
assert_eq!(
|
||||||
|
m.get("header.version"),
|
||||||
|
Some(&ByteRange { start: 4, end: 5 })
|
||||||
|
);
|
||||||
assert_eq!(m.get("body"), Some(&ByteRange { start: 8, end: 12 }));
|
assert_eq!(m.get("body"), Some(&ByteRange { start: 8, end: 12 }));
|
||||||
assert_eq!(m.total_size(), 12);
|
assert_eq!(m.total_size(), 12);
|
||||||
}
|
}
|
||||||
@@ -881,10 +894,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let m = map(&schema);
|
let m = map(&schema);
|
||||||
assert_eq!(
|
assert_eq!(m.get("event.type"), Some(&ByteRange { start: 0, end: 1 }));
|
||||||
m.get("event.type"),
|
|
||||||
Some(&ByteRange { start: 0, end: 1 })
|
|
||||||
);
|
|
||||||
assert_eq!(m.total_size(), 12);
|
assert_eq!(m.total_size(), 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -952,7 +962,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compute_rejects_non_struct_top_level() {
|
fn compute_rejects_non_struct_top_level() {
|
||||||
let schema = json!({ "TypeDef:Union": true, "discriminator": { "kind": "byte" }, "mapping": {} });
|
let schema =
|
||||||
|
json!({ "TypeDef:Union": true, "discriminator": { "kind": "byte" }, "mapping": {} });
|
||||||
let err = OffsetMap::compute(&schema).unwrap_err();
|
let err = OffsetMap::compute(&schema).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Schema(_)));
|
assert!(matches!(err, TypedefError::Schema(_)));
|
||||||
}
|
}
|
||||||
@@ -973,4 +984,4 @@ mod tests {
|
|||||||
assert_eq!(empty.len(), 0);
|
assert_eq!(empty.len(), 0);
|
||||||
assert!(empty.is_empty());
|
assert!(empty.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
const TYPEDEF_PREFIX: &str = "TypeDef:";
|
const TYPEDEF_PREFIX: &str = "TypeDef:";
|
||||||
|
|
||||||
const BYTE_DISCRIMINATOR_TYPES: &[&str] = &[
|
const BYTE_DISCRIMINATOR_TYPES: &[&str] = &["TypeDef:Uint8", "TypeDef:Uint16", "TypeDef:Uint32"];
|
||||||
"TypeDef:Uint8",
|
|
||||||
"TypeDef:Uint16",
|
|
||||||
"TypeDef:Uint32",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Returns the `TypeDef:*` kind string if the schema node declares one.
|
/// Returns the `TypeDef:*` kind string if the schema node declares one.
|
||||||
/// Returns `None` if the node has no `TypeDef:*` keyword.
|
/// Returns `None` if the node has no `TypeDef:*` keyword.
|
||||||
@@ -48,13 +44,8 @@ pub fn type_size(kind: &str) -> Option<usize> {
|
|||||||
"TypeDef:Float64" => Some(8),
|
"TypeDef:Float64" => Some(8),
|
||||||
"TypeDef:Int8" | "TypeDef:Uint8" | "TypeDef:Boolean" => Some(1),
|
"TypeDef:Int8" | "TypeDef:Uint8" | "TypeDef:Boolean" => Some(1),
|
||||||
"TypeDef:Int16" | "TypeDef:Uint16" => Some(2),
|
"TypeDef:Int16" | "TypeDef:Uint16" => Some(2),
|
||||||
"TypeDef:String"
|
"TypeDef:String" | "TypeDef:Bytes" | "TypeDef:Struct" | "TypeDef:Union"
|
||||||
| "TypeDef:Bytes"
|
| "TypeDef:Array" | "TypeDef:Record" | "TypeDef:Timestamp" => None,
|
||||||
| "TypeDef:Struct"
|
|
||||||
| "TypeDef:Union"
|
|
||||||
| "TypeDef:Array"
|
|
||||||
| "TypeDef:Record"
|
|
||||||
| "TypeDef:Timestamp" => None,
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +148,11 @@ pub fn parse_max_length(node: &Value) -> Option<usize> {
|
|||||||
/// Parse the `"endian"` annotation. Defaults to `Little` if absent or
|
/// Parse the `"endian"` annotation. Defaults to `Little` if absent or
|
||||||
/// unrecognized. Operates on any node, not just the root.
|
/// unrecognized. Operates on any node, not just the root.
|
||||||
pub fn parse_endian(node: &Value) -> Endian {
|
pub fn parse_endian(node: &Value) -> Endian {
|
||||||
match node.as_object().and_then(|o| o.get("endian")).and_then(Value::as_str) {
|
match node
|
||||||
|
.as_object()
|
||||||
|
.and_then(|o| o.get("endian"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
{
|
||||||
Some("big") => Endian::Big,
|
Some("big") => Endian::Big,
|
||||||
_ => Endian::Little,
|
_ => Endian::Little,
|
||||||
}
|
}
|
||||||
@@ -196,18 +191,16 @@ pub fn parse_discriminator(node: &Value) -> Result<DiscriminatorKind, TypedefErr
|
|||||||
let disc = obj.get("discriminator").ok_or_else(|| {
|
let disc = obj.get("discriminator").ok_or_else(|| {
|
||||||
TypedefError::Schema("union is missing 'discriminator' annotation".to_string())
|
TypedefError::Schema("union is missing 'discriminator' annotation".to_string())
|
||||||
})?;
|
})?;
|
||||||
let disc_obj = disc.as_object().ok_or_else(|| {
|
let disc_obj = disc
|
||||||
TypedefError::Schema("'discriminator' must be an object".to_string())
|
.as_object()
|
||||||
})?;
|
.ok_or_else(|| TypedefError::Schema("'discriminator' must be an object".to_string()))?;
|
||||||
let kind = disc_obj.get("kind").and_then(Value::as_str).ok_or_else(|| {
|
let kind = disc_obj
|
||||||
TypedefError::Schema("discriminator is missing 'kind' field".to_string())
|
.get("kind")
|
||||||
})?;
|
.and_then(Value::as_str)
|
||||||
|
.ok_or_else(|| TypedefError::Schema("discriminator is missing 'kind' field".to_string()))?;
|
||||||
match kind {
|
match kind {
|
||||||
"byte" => {
|
"byte" => {
|
||||||
let offset = disc_obj
|
let offset = disc_obj.get("offset").and_then(Value::as_u64).unwrap_or(0) as usize;
|
||||||
.get("offset")
|
|
||||||
.and_then(Value::as_u64)
|
|
||||||
.unwrap_or(0) as usize;
|
|
||||||
let disc_type = disc_obj
|
let disc_type = disc_obj
|
||||||
.get("type")
|
.get("type")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
@@ -377,8 +370,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn endian_from_schema_defaults_to_little() {
|
fn endian_from_schema_defaults_to_little() {
|
||||||
assert_eq!(Endian::from_schema(&json!({})), Endian::Little);
|
assert_eq!(Endian::from_schema(&json!({})), Endian::Little);
|
||||||
assert_eq!(Endian::from_schema(&json!({"endian": "little"})), Endian::Little);
|
assert_eq!(
|
||||||
assert_eq!(Endian::from_schema(&json!({"endian": "weird"})), Endian::Little);
|
Endian::from_schema(&json!({"endian": "little"})),
|
||||||
|
Endian::Little
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Endian::from_schema(&json!({"endian": "weird"})),
|
||||||
|
Endian::Little
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -388,7 +387,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_encoding_shorthand_true() {
|
fn parse_encoding_shorthand_true() {
|
||||||
assert_eq!(parse_encoding(&json!(true)), VariableEncoding::LengthPrefixed);
|
assert_eq!(
|
||||||
|
parse_encoding(&json!(true)),
|
||||||
|
VariableEncoding::LengthPrefixed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -414,7 +416,10 @@ mod tests {
|
|||||||
VariableEncoding::LengthPrefixed
|
VariableEncoding::LengthPrefixed
|
||||||
);
|
);
|
||||||
assert_eq!(parse_encoding(&json!(42)), VariableEncoding::LengthPrefixed);
|
assert_eq!(parse_encoding(&json!(42)), VariableEncoding::LengthPrefixed);
|
||||||
assert_eq!(parse_encoding(&json!(null)), VariableEncoding::LengthPrefixed);
|
assert_eq!(
|
||||||
|
parse_encoding(&json!(null)),
|
||||||
|
VariableEncoding::LengthPrefixed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -450,10 +455,13 @@ mod tests {
|
|||||||
fn parse_discriminator_byte_default_offset_and_type() {
|
fn parse_discriminator_byte_default_offset_and_type() {
|
||||||
let schema = json!({"discriminator": {"kind": "byte"}});
|
let schema = json!({"discriminator": {"kind": "byte"}});
|
||||||
let disc = parse_discriminator(&schema).expect("byte discriminator");
|
let disc = parse_discriminator(&schema).expect("byte discriminator");
|
||||||
assert_eq!(disc, DiscriminatorKind::Byte {
|
assert_eq!(
|
||||||
offset: 0,
|
disc,
|
||||||
disc_type: "TypeDef:Uint8".to_string(),
|
DiscriminatorKind::Byte {
|
||||||
});
|
offset: 0,
|
||||||
|
disc_type: "TypeDef:Uint8".to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -462,19 +470,25 @@ mod tests {
|
|||||||
"discriminator": {"kind": "byte", "offset": 4, "type": "TypeDef:Uint16"}
|
"discriminator": {"kind": "byte", "offset": 4, "type": "TypeDef:Uint16"}
|
||||||
});
|
});
|
||||||
let disc = parse_discriminator(&schema).expect("byte discriminator");
|
let disc = parse_discriminator(&schema).expect("byte discriminator");
|
||||||
assert_eq!(disc, DiscriminatorKind::Byte {
|
assert_eq!(
|
||||||
offset: 4,
|
disc,
|
||||||
disc_type: "TypeDef:Uint16".to_string(),
|
DiscriminatorKind::Byte {
|
||||||
});
|
offset: 4,
|
||||||
|
disc_type: "TypeDef:Uint16".to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_discriminator_field() {
|
fn parse_discriminator_field() {
|
||||||
let schema = json!({"discriminator": {"kind": "field", "name": "type"}});
|
let schema = json!({"discriminator": {"kind": "field", "name": "type"}});
|
||||||
let disc = parse_discriminator(&schema).expect("field discriminator");
|
let disc = parse_discriminator(&schema).expect("field discriminator");
|
||||||
assert_eq!(disc, DiscriminatorKind::Field {
|
assert_eq!(
|
||||||
name: "type".to_string()
|
disc,
|
||||||
});
|
DiscriminatorKind::Field {
|
||||||
|
name: "type".to_string()
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -580,4 +594,4 @@ mod tests {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,9 +116,8 @@ impl SequentialReader {
|
|||||||
/// Returns [`TypedefError::Schema`] if the schema is not an object,
|
/// Returns [`TypedefError::Schema`] if the schema is not an object,
|
||||||
/// does not declare `TypeDef:Struct`, or has no `properties` object.
|
/// does not declare `TypeDef:Struct`, or has no `properties` object.
|
||||||
pub fn new(schema: &Value) -> Result<Self, TypedefError> {
|
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)
|
||||||
TypedefError::Schema("schema has no TypeDef:* kind".to_string())
|
.ok_or_else(|| TypedefError::Schema("schema has no TypeDef:* kind".to_string()))?;
|
||||||
})?;
|
|
||||||
if kind != "TypeDef:Struct" {
|
if kind != "TypeDef:Struct" {
|
||||||
return Err(TypedefError::Schema(format!(
|
return Err(TypedefError::Schema(format!(
|
||||||
"SequentialReader only supports TypeDef:Struct at the top level, got {kind}"
|
"SequentialReader only supports TypeDef:Struct at the top level, got {kind}"
|
||||||
@@ -143,7 +142,7 @@ impl SequentialReader {
|
|||||||
field_index: 0,
|
field_index: 0,
|
||||||
position: 0,
|
position: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the next field from `buffer` at the current position.
|
/// Read the next field from `buffer` at the current position.
|
||||||
///
|
///
|
||||||
@@ -407,20 +406,22 @@ fn read_union_value<'a>(
|
|||||||
.as_object()
|
.as_object()
|
||||||
.and_then(|obj| obj.get("mapping"))
|
.and_then(|obj| obj.get("mapping"))
|
||||||
.and_then(Value::as_object)
|
.and_then(Value::as_object)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| TypedefError::Schema(format!("union {field_path} has no mapping object")))?;
|
||||||
TypedefError::Schema(format!("union {field_path} has no mapping object"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match disc {
|
match disc {
|
||||||
DiscriminatorKind::Byte { offset: disc_offset, disc_type } => {
|
DiscriminatorKind::Byte {
|
||||||
let abs_offset = offset
|
offset: disc_offset,
|
||||||
.checked_add(disc_offset)
|
disc_type,
|
||||||
.ok_or_else(|| TypedefError::Access {
|
} => {
|
||||||
field_path: field_path.to_string(),
|
let abs_offset =
|
||||||
reason: format!(
|
offset
|
||||||
"discriminator offset {offset} + {disc_offset} overflows usize"
|
.checked_add(disc_offset)
|
||||||
),
|
.ok_or_else(|| TypedefError::Access {
|
||||||
})?;
|
field_path: field_path.to_string(),
|
||||||
|
reason: format!(
|
||||||
|
"discriminator offset {offset} + {disc_offset} overflows usize"
|
||||||
|
),
|
||||||
|
})?;
|
||||||
let (disc_value, disc_size) =
|
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 key = disc_value.to_string();
|
||||||
@@ -428,14 +429,13 @@ fn read_union_value<'a>(
|
|||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!("unknown union discriminator value: {key}"),
|
reason: format!("unknown union discriminator value: {key}"),
|
||||||
})?;
|
})?;
|
||||||
let variant_start = abs_offset
|
let variant_start =
|
||||||
.checked_add(disc_size)
|
abs_offset
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.checked_add(disc_size)
|
||||||
field_path: field_path.to_string(),
|
.ok_or_else(|| TypedefError::Access {
|
||||||
reason: format!(
|
field_path: field_path.to_string(),
|
||||||
"variant start {abs_offset} + {disc_size} overflows usize"
|
reason: format!("variant start {abs_offset} + {disc_size} overflows usize"),
|
||||||
),
|
})?;
|
||||||
})?;
|
|
||||||
let variant_size = resolve_and_walk_variant(
|
let variant_size = resolve_and_walk_variant(
|
||||||
root_schema,
|
root_schema,
|
||||||
schema,
|
schema,
|
||||||
@@ -445,14 +445,15 @@ fn read_union_value<'a>(
|
|||||||
endian,
|
endian,
|
||||||
field_path,
|
field_path,
|
||||||
)?;
|
)?;
|
||||||
let end = variant_start
|
let end =
|
||||||
.checked_add(variant_size)
|
variant_start
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.checked_add(variant_size)
|
||||||
field_path: field_path.to_string(),
|
.ok_or_else(|| TypedefError::Access {
|
||||||
reason: format!(
|
field_path: field_path.to_string(),
|
||||||
"union end {variant_start} + {variant_size} overflows usize"
|
reason: format!(
|
||||||
),
|
"union end {variant_start} + {variant_size} overflows usize"
|
||||||
})?;
|
),
|
||||||
|
})?;
|
||||||
Ok((
|
Ok((
|
||||||
FieldValue::Union {
|
FieldValue::Union {
|
||||||
discriminator: key,
|
discriminator: key,
|
||||||
@@ -476,14 +477,8 @@ fn read_union_value<'a>(
|
|||||||
"union {field_path} has no discriminator field '{name}'"
|
"union {field_path} has no discriminator field '{name}'"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let (disc_value, after_disc) = read_field_value(
|
let (disc_value, after_disc) =
|
||||||
buffer,
|
read_field_value(buffer, root_schema, disc_schema, field_path, offset, endian)?;
|
||||||
root_schema,
|
|
||||||
disc_schema,
|
|
||||||
field_path,
|
|
||||||
offset,
|
|
||||||
endian,
|
|
||||||
)?;
|
|
||||||
let key = discriminator_string_value(&disc_value, field_path)?;
|
let key = discriminator_string_value(&disc_value, field_path)?;
|
||||||
let variant_schema = mapping.get(&key).ok_or_else(|| TypedefError::Access {
|
let variant_schema = mapping.get(&key).ok_or_else(|| TypedefError::Access {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
@@ -502,9 +497,7 @@ fn read_union_value<'a>(
|
|||||||
.checked_add(variant_size)
|
.checked_add(variant_size)
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.ok_or_else(|| TypedefError::Access {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!(
|
reason: format!("union end {after_disc} + {variant_size} overflows usize"),
|
||||||
"union end {after_disc} + {variant_size} overflows usize"
|
|
||||||
),
|
|
||||||
})?;
|
})?;
|
||||||
Ok((
|
Ok((
|
||||||
FieldValue::Union {
|
FieldValue::Union {
|
||||||
@@ -548,7 +541,10 @@ fn read_byte_discriminator(
|
|||||||
/// Stringify a field-name discriminator value. Only the common kinds
|
/// Stringify a field-name discriminator value. Only the common kinds
|
||||||
/// (String, Uint8/16/32, Enum) are supported — anything else is a schema
|
/// (String, Uint8/16/32, Enum) are supported — anything else is a schema
|
||||||
/// error.
|
/// error.
|
||||||
fn discriminator_string_value(value: &FieldValue<'_>, field_path: &str) -> Result<String, TypedefError> {
|
fn discriminator_string_value(
|
||||||
|
value: &FieldValue<'_>,
|
||||||
|
field_path: &str,
|
||||||
|
) -> Result<String, TypedefError> {
|
||||||
match value {
|
match value {
|
||||||
FieldValue::String(s) => Ok(s.to_string()),
|
FieldValue::String(s) => Ok(s.to_string()),
|
||||||
FieldValue::U8(v) => Ok(v.to_string()),
|
FieldValue::U8(v) => Ok(v.to_string()),
|
||||||
@@ -577,12 +573,18 @@ fn read_array_value<'a>(
|
|||||||
let obj = schema.as_object().ok_or_else(|| {
|
let obj = schema.as_object().ok_or_else(|| {
|
||||||
TypedefError::Schema(format!("array {field_path} schema is not an object"))
|
TypedefError::Schema(format!("array {field_path} schema is not an object"))
|
||||||
})?;
|
})?;
|
||||||
let items_schema = obj.get("items").ok_or_else(|| {
|
let items_schema = obj
|
||||||
TypedefError::Schema(format!("array {field_path} has no items schema"))
|
.get("items")
|
||||||
})?;
|
.ok_or_else(|| TypedefError::Schema(format!("array {field_path} has no items schema")))?;
|
||||||
|
|
||||||
let min = obj.get("minItems").and_then(Value::as_u64).map(|n| n as u32);
|
let min = obj
|
||||||
let max = obj.get("maxItems").and_then(Value::as_u64).map(|n| n as u32);
|
.get("minItems")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.map(|n| n as u32);
|
||||||
|
let max = obj
|
||||||
|
.get("maxItems")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.map(|n| n as u32);
|
||||||
let fixed_count = matches!((min, max), (Some(a), Some(b)) if a == b);
|
let fixed_count = matches!((min, max), (Some(a), Some(b)) if a == b);
|
||||||
let (count, element_start) = if fixed_count {
|
let (count, element_start) = if fixed_count {
|
||||||
let count = min.ok_or_else(|| {
|
let count = min.ok_or_else(|| {
|
||||||
@@ -623,9 +625,7 @@ fn read_array_value<'a>(
|
|||||||
.checked_mul(element_stride)
|
.checked_mul(element_stride)
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.ok_or_else(|| TypedefError::Access {
|
||||||
field_path: field_path.to_string(),
|
field_path: field_path.to_string(),
|
||||||
reason: format!(
|
reason: format!("array size {count} × stride {element_stride} overflows usize"),
|
||||||
"array size {count} × stride {element_stride} overflows usize"
|
|
||||||
),
|
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -672,9 +672,7 @@ fn walk_variable_array_size(
|
|||||||
if new_position < position {
|
if new_position < position {
|
||||||
return Err(TypedefError::Access {
|
return Err(TypedefError::Access {
|
||||||
field_path: element_path,
|
field_path: element_path,
|
||||||
reason: format!(
|
reason: format!("array element walked backwards: {position} → {new_position}"),
|
||||||
"array element walked backwards: {position} → {new_position}"
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
position = new_position;
|
position = new_position;
|
||||||
@@ -698,9 +696,7 @@ fn read_record_value<'a>(
|
|||||||
let value_schema = schema
|
let value_schema = schema
|
||||||
.as_object()
|
.as_object()
|
||||||
.and_then(|obj| obj.get("values"))
|
.and_then(|obj| obj.get("values"))
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| TypedefError::Schema(format!("record {field_path} has no values schema")))?;
|
||||||
TypedefError::Schema(format!("record {field_path} has no values schema"))
|
|
||||||
})?;
|
|
||||||
let mut position = offset + U32_SIZE;
|
let mut position = offset + U32_SIZE;
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let entry_path = format!("{field_path}[{i}].key");
|
let entry_path = format!("{field_path}[{i}].key");
|
||||||
@@ -733,11 +729,11 @@ fn resolve_and_walk_variant(
|
|||||||
endian: Endian,
|
endian: Endian,
|
||||||
field_path: &str,
|
field_path: &str,
|
||||||
) -> Result<usize, TypedefError> {
|
) -> Result<usize, TypedefError> {
|
||||||
let resolved = resolve_variant_schema(root_schema, union_schema, variant_schema)
|
let resolved =
|
||||||
.ok_or_else(|| {
|
resolve_variant_schema(root_schema, union_schema, variant_schema).ok_or_else(|| {
|
||||||
TypedefError::Schema(format!(
|
TypedefError::Schema(format!(
|
||||||
"union {field_path} variant could not be resolved: {variant_schema}"
|
"union {field_path} variant could not be resolved: {variant_schema}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let kind = schema::get_typedef_kind(resolved).ok_or_else(|| {
|
let kind = schema::get_typedef_kind(resolved).ok_or_else(|| {
|
||||||
TypedefError::Schema(format!(
|
TypedefError::Schema(format!(
|
||||||
@@ -745,9 +741,7 @@ fn resolve_and_walk_variant(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
match kind {
|
match kind {
|
||||||
"TypeDef:Struct" => {
|
"TypeDef:Struct" => walk_struct_size(root_schema, resolved, buffer, variant_start, endian),
|
||||||
walk_struct_size(root_schema, resolved, buffer, variant_start, endian)
|
|
||||||
}
|
|
||||||
"TypeDef:Union" => {
|
"TypeDef:Union" => {
|
||||||
let (_, end) = read_union_value(
|
let (_, end) = read_union_value(
|
||||||
buffer,
|
buffer,
|
||||||
@@ -834,9 +828,7 @@ fn walk_struct_size(
|
|||||||
if new_position < position {
|
if new_position < position {
|
||||||
return Err(TypedefError::Access {
|
return Err(TypedefError::Access {
|
||||||
field_path: name.clone(),
|
field_path: name.clone(),
|
||||||
reason: format!(
|
reason: format!("struct field walked backwards: {position} → {new_position}"),
|
||||||
"struct field walked backwards: {position} → {new_position}"
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
position = new_position;
|
position = new_position;
|
||||||
@@ -1150,7 +1142,8 @@ mod tests {
|
|||||||
assert_eq!(end, 3);
|
assert_eq!(end, 3);
|
||||||
let inner_schema = &nested["properties"]["inner"];
|
let inner_schema = &nested["properties"]["inner"];
|
||||||
let inner_reader = SequentialReader::new(inner_schema).unwrap();
|
let inner_reader = SequentialReader::new(inner_schema).unwrap();
|
||||||
let inner_end = walk_struct_size(inner_schema, inner_schema, &buf, start, LE).unwrap();
|
let inner_end =
|
||||||
|
walk_struct_size(inner_schema, inner_schema, &buf, start, LE).unwrap();
|
||||||
assert_eq!(inner_end, end - start);
|
assert_eq!(inner_end, end - start);
|
||||||
let _ = inner_reader;
|
let _ = inner_reader;
|
||||||
}
|
}
|
||||||
@@ -1191,7 +1184,10 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "packet");
|
assert_eq!(name, "packet");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Union { discriminator, variant_start } => {
|
FieldValue::Union {
|
||||||
|
discriminator,
|
||||||
|
variant_start,
|
||||||
|
} => {
|
||||||
assert_eq!(discriminator, "5");
|
assert_eq!(discriminator, "5");
|
||||||
assert_eq!(variant_start, 1);
|
assert_eq!(variant_start, 1);
|
||||||
}
|
}
|
||||||
@@ -1230,7 +1226,10 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "event");
|
assert_eq!(name, "event");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Union { discriminator, variant_start } => {
|
FieldValue::Union {
|
||||||
|
discriminator,
|
||||||
|
variant_start,
|
||||||
|
} => {
|
||||||
assert_eq!(discriminator, "read");
|
assert_eq!(discriminator, "read");
|
||||||
assert_eq!(variant_start, after);
|
assert_eq!(variant_start, after);
|
||||||
}
|
}
|
||||||
@@ -1258,7 +1257,11 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "vals");
|
assert_eq!(name, "vals");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Array { count, element_start, element_stride } => {
|
FieldValue::Array {
|
||||||
|
count,
|
||||||
|
element_start,
|
||||||
|
element_stride,
|
||||||
|
} => {
|
||||||
assert_eq!(count, 3);
|
assert_eq!(count, 3);
|
||||||
assert_eq!(element_start, 0);
|
assert_eq!(element_start, 0);
|
||||||
assert_eq!(element_stride, 1);
|
assert_eq!(element_stride, 1);
|
||||||
@@ -1288,7 +1291,11 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "vals");
|
assert_eq!(name, "vals");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Array { count, element_start, element_stride } => {
|
FieldValue::Array {
|
||||||
|
count,
|
||||||
|
element_start,
|
||||||
|
element_stride,
|
||||||
|
} => {
|
||||||
assert_eq!(count, 2);
|
assert_eq!(count, 2);
|
||||||
assert_eq!(element_start, 4);
|
assert_eq!(element_start, 4);
|
||||||
assert_eq!(element_stride, 2);
|
assert_eq!(element_stride, 2);
|
||||||
@@ -1319,7 +1326,11 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "items");
|
assert_eq!(name, "items");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Array { count, element_start, element_stride } => {
|
FieldValue::Array {
|
||||||
|
count,
|
||||||
|
element_start,
|
||||||
|
element_stride,
|
||||||
|
} => {
|
||||||
assert_eq!(count, 2);
|
assert_eq!(count, 2);
|
||||||
assert_eq!(element_start, 4);
|
assert_eq!(element_start, 4);
|
||||||
assert_eq!(element_stride, 0);
|
assert_eq!(element_stride, 0);
|
||||||
@@ -1423,7 +1434,10 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "packet");
|
assert_eq!(name, "packet");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Union { discriminator, variant_start } => {
|
FieldValue::Union {
|
||||||
|
discriminator,
|
||||||
|
variant_start,
|
||||||
|
} => {
|
||||||
assert_eq!(discriminator, "5");
|
assert_eq!(discriminator, "5");
|
||||||
assert_eq!(variant_start, 1);
|
assert_eq!(variant_start, 1);
|
||||||
}
|
}
|
||||||
@@ -1457,7 +1471,10 @@ mod tests {
|
|||||||
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
|
||||||
assert_eq!(name, "packet");
|
assert_eq!(name, "packet");
|
||||||
match value {
|
match value {
|
||||||
FieldValue::Union { discriminator, variant_start } => {
|
FieldValue::Union {
|
||||||
|
discriminator,
|
||||||
|
variant_start,
|
||||||
|
} => {
|
||||||
assert_eq!(discriminator, "5");
|
assert_eq!(discriminator, "5");
|
||||||
assert_eq!(variant_start, 1);
|
assert_eq!(variant_start, 1);
|
||||||
}
|
}
|
||||||
@@ -1517,4 +1534,4 @@ mod tests {
|
|||||||
assert_eq!(value, FieldValue::U8(7));
|
assert_eq!(value, FieldValue::U8(7));
|
||||||
assert_eq!(reader.position(), expected_end);
|
assert_eq!(reader.position(), expected_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,14 +79,15 @@ pub fn read_byte_discriminator(
|
|||||||
let key = disc_value.to_string();
|
let key = disc_value.to_string();
|
||||||
verify_mapping_key(union_schema, &key, DISCRIMINATOR_PATH, &key)?;
|
verify_mapping_key(union_schema, &key, DISCRIMINATOR_PATH, &key)?;
|
||||||
|
|
||||||
let variant_offset = offset
|
let variant_offset =
|
||||||
.checked_add(discriminator_size)
|
offset
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.checked_add(discriminator_size)
|
||||||
field_path: DISCRIMINATOR_PATH.to_string(),
|
.ok_or_else(|| TypedefError::Access {
|
||||||
reason: format!(
|
field_path: DISCRIMINATOR_PATH.to_string(),
|
||||||
"offset {offset} + discriminator_size {discriminator_size} overflows usize"
|
reason: format!(
|
||||||
),
|
"offset {offset} + discriminator_size {discriminator_size} overflows usize"
|
||||||
})?;
|
),
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(UnionDispatch {
|
Ok(UnionDispatch {
|
||||||
key,
|
key,
|
||||||
@@ -149,15 +150,16 @@ pub fn read_field_discriminator(
|
|||||||
let (key, discriminator_field_size) = match kind {
|
let (key, discriminator_field_size) = match kind {
|
||||||
"TypeDef:String" => {
|
"TypeDef:String" => {
|
||||||
let s = read_string(buffer, disc_field_offset, &name, endian)?;
|
let s = read_string(buffer, disc_field_offset, &name, endian)?;
|
||||||
let size = STRING_PREFIX_SIZE
|
let size =
|
||||||
.checked_add(s.len())
|
STRING_PREFIX_SIZE
|
||||||
.ok_or_else(|| TypedefError::Access {
|
.checked_add(s.len())
|
||||||
field_path: name.clone(),
|
.ok_or_else(|| TypedefError::Access {
|
||||||
reason: format!(
|
field_path: name.clone(),
|
||||||
"string prefix {STRING_PREFIX_SIZE} + data length {} overflows usize",
|
reason: format!(
|
||||||
s.len()
|
"string prefix {STRING_PREFIX_SIZE} + data length {} overflows usize",
|
||||||
),
|
s.len()
|
||||||
})?;
|
),
|
||||||
|
})?;
|
||||||
(s.to_string(), size)
|
(s.to_string(), size)
|
||||||
}
|
}
|
||||||
"TypeDef:Uint8" => {
|
"TypeDef:Uint8" => {
|
||||||
@@ -208,10 +210,7 @@ pub fn read_field_discriminator(
|
|||||||
/// - [`TypedefError::Schema`] if the union has no `mapping` object, the
|
/// - [`TypedefError::Schema`] if the union has no `mapping` object, the
|
||||||
/// `key` is not present, a `$ref` is malformed, or a `$ref` cannot be
|
/// `key` is not present, a `$ref` is malformed, or a `$ref` cannot be
|
||||||
/// resolved against the union schema's own `$defs`.
|
/// resolved against the union schema's own `$defs`.
|
||||||
pub fn resolve_variant<'a>(
|
pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Value, TypedefError> {
|
||||||
union_schema: &'a Value,
|
|
||||||
key: &str,
|
|
||||||
) -> Result<&'a Value, TypedefError> {
|
|
||||||
let mapping = union_schema
|
let mapping = union_schema
|
||||||
.get("mapping")
|
.get("mapping")
|
||||||
.and_then(Value::as_object)
|
.and_then(Value::as_object)
|
||||||
@@ -643,4 +642,4 @@ mod tests {
|
|||||||
let err = discriminator_size(&schema).unwrap_err();
|
let err = discriminator_size(&schema).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Schema(_)));
|
assert!(matches!(err, TypedefError::Schema(_)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,9 @@ impl Keyword for Float32Validator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_f64() {
|
match instance.as_f64() {
|
||||||
Some(f) if f.is_finite() => Ok(()),
|
Some(f) if f.is_finite() => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected a finite f32-compatible number")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected a finite f32-compatible number",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -85,7 +87,9 @@ impl Keyword for Int8Validator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_i64() {
|
match instance.as_i64() {
|
||||||
Some(n) if (-128..=127).contains(&n) => Ok(()),
|
Some(n) if (-128..=127).contains(&n) => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected an integer in range [-128, 127]")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected an integer in range [-128, 127]",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -98,7 +102,9 @@ impl Keyword for Int16Validator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_i64() {
|
match instance.as_i64() {
|
||||||
Some(n) if (-32_768..=32_767).contains(&n) => Ok(()),
|
Some(n) if (-32_768..=32_767).contains(&n) => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected an integer in range [-32768, 32767]")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected an integer in range [-32768, 32767]",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -130,7 +136,9 @@ impl Keyword for Uint8Validator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_u64() {
|
match instance.as_u64() {
|
||||||
Some(n) if n <= 255 => Ok(()),
|
Some(n) if n <= 255 => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected an unsigned integer in range [0, 255]")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected an unsigned integer in range [0, 255]",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -143,7 +151,9 @@ impl Keyword for Uint16Validator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_u64() {
|
match instance.as_u64() {
|
||||||
Some(n) if n <= 65_535 => Ok(()),
|
Some(n) if n <= 65_535 => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected an unsigned integer in range [0, 65535]")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected an unsigned integer in range [0, 65535]",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -191,9 +201,9 @@ impl Keyword for StringValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
instance.as_str().is_some_and(|s| {
|
instance
|
||||||
self.max_length.is_none_or(|max| s.len() <= max)
|
.as_str()
|
||||||
})
|
.is_some_and(|s| self.max_length.is_none_or(|max| s.len() <= max))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,9 +228,9 @@ impl Keyword for BytesValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
instance.as_str().is_some_and(|s| {
|
instance
|
||||||
self.max_length.is_none_or(|max| s.len() <= max)
|
.as_str()
|
||||||
})
|
.is_some_and(|s| self.max_length.is_none_or(|max| s.len() <= max))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +252,9 @@ impl Keyword for TimestampValidator {
|
|||||||
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
|
||||||
match instance.as_str() {
|
match instance.as_str() {
|
||||||
Some(s) if is_rfc3339_timestamp(s) => Ok(()),
|
Some(s) if is_rfc3339_timestamp(s) => Ok(()),
|
||||||
_ => Err(ValidationError::custom("expected an RFC 3339 timestamp string")),
|
_ => Err(ValidationError::custom(
|
||||||
|
"expected an RFC 3339 timestamp string",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(&self, instance: &Value) -> bool {
|
fn is_valid(&self, instance: &Value) -> bool {
|
||||||
@@ -277,11 +289,13 @@ fn is_rfc3339_timestamp(s: &str) -> bool {
|
|||||||
if time_parts.len() < 2 || time_parts.len() > 3 {
|
if time_parts.len() < 2 || time_parts.len() > 3 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
date_parts[0]
|
date_parts[0].parse::<u16>().is_ok_and(|y| y > 0)
|
||||||
.parse::<u16>()
|
&& date_parts[1]
|
||||||
.is_ok_and(|y| y > 0)
|
.parse::<u8>()
|
||||||
&& date_parts[1].parse::<u8>().is_ok_and(|m| (1..=12).contains(&m))
|
.is_ok_and(|m| (1..=12).contains(&m))
|
||||||
&& date_parts[2].parse::<u8>().is_ok_and(|d| (1..=31).contains(&d))
|
&& date_parts[2]
|
||||||
|
.parse::<u8>()
|
||||||
|
.is_ok_and(|d| (1..=31).contains(&d))
|
||||||
&& time_parts[0].parse::<u8>().is_ok_and(|h| h <= 23)
|
&& time_parts[0].parse::<u8>().is_ok_and(|h| h <= 23)
|
||||||
&& time_parts[1].parse::<u8>().is_ok_and(|m| m <= 59)
|
&& time_parts[1].parse::<u8>().is_ok_and(|m| m <= 59)
|
||||||
}
|
}
|
||||||
@@ -378,7 +392,9 @@ fn float32_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(Float32Validator))
|
Ok(Box::new(Float32Validator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Float32 must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Float32 must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +406,9 @@ fn float64_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(Float64Validator))
|
Ok(Box::new(Float64Validator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Float64 must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Float64 must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +468,9 @@ fn uint16_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(Uint16Validator))
|
Ok(Box::new(Uint16Validator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Uint16 must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Uint16 must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +482,9 @@ fn uint32_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(Uint32Validator))
|
Ok(Box::new(Uint32Validator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Uint32 must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Uint32 must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +496,9 @@ fn boolean_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(BooleanValidator))
|
Ok(Box::new(BooleanValidator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Boolean must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Boolean must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +508,9 @@ fn string_factory<'a>(
|
|||||||
_path: jsonschema::paths::Location,
|
_path: jsonschema::paths::Location,
|
||||||
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
|
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
|
||||||
if value.as_bool() != Some(true) {
|
if value.as_bool() != Some(true) {
|
||||||
return Err(ValidationError::schema("TypeDef:String must be set to true"));
|
return Err(ValidationError::schema(
|
||||||
|
"TypeDef:String must be set to true",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let max_length = parent
|
let max_length = parent
|
||||||
.get("maxLength")
|
.get("maxLength")
|
||||||
@@ -528,7 +554,9 @@ fn struct_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(StructValidator))
|
Ok(Box::new(StructValidator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Struct must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Struct must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +592,9 @@ fn record_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(RecordValidator))
|
Ok(Box::new(RecordValidator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Record must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Record must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +606,9 @@ fn timestamp_factory<'a>(
|
|||||||
if value.as_bool() == Some(true) {
|
if value.as_bool() == Some(true) {
|
||||||
Ok(Box::new(TimestampValidator))
|
Ok(Box::new(TimestampValidator))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::schema("TypeDef:Timestamp must be set to true"))
|
Err(ValidationError::schema(
|
||||||
|
"TypeDef:Timestamp must be set to true",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,4 +882,4 @@ mod tests {
|
|||||||
let err = build_validator(&schema).expect_err("schema must be an object");
|
let err = build_validator(&schema).expect_err("schema must be an object");
|
||||||
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
|
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,8 +201,14 @@ fn read_write_aligned_round_trips_all_fixed_size_kinds() -> Result<(), TypedefEr
|
|||||||
assert_eq!(engine.read_field(&buffer, "u8")?, FieldValue::U8(0xAB));
|
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, "i16")?, FieldValue::I16(-32000));
|
||||||
assert_eq!(engine.read_field(&buffer, "u16")?, FieldValue::U16(0xBEEF));
|
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!(
|
||||||
assert_eq!(engine.read_field(&buffer, "u32")?, FieldValue::U32(0xDEADBEEF));
|
engine.read_field(&buffer, "i32")?,
|
||||||
|
FieldValue::I32(-2_000_000_007)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.read_field(&buffer, "u32")?,
|
||||||
|
FieldValue::U32(0xDEADBEEF)
|
||||||
|
);
|
||||||
assert_eq!(engine.read_field(&buffer, "f32")?, FieldValue::F32(1.5));
|
assert_eq!(engine.read_field(&buffer, "f32")?, FieldValue::F32(1.5));
|
||||||
assert_eq!(engine.read_field(&buffer, "f64")?, FieldValue::F64(2.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, "b")?, FieldValue::Bool(true));
|
||||||
@@ -368,4 +374,4 @@ fn read_field_aligned_reads_nested_struct_byte_range() -> Result<(), TypedefErro
|
|||||||
FieldValue::U32(0xCAFEBABE)
|
FieldValue::U32(0xCAFEBABE)
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
//! variable-length field size in `LayoutBuilder::build`).
|
//! variable-length field size in `LayoutBuilder::build`).
|
||||||
|
|
||||||
use alknet_typedef::data_access;
|
use alknet_typedef::data_access;
|
||||||
use alknet_typedef::*;
|
|
||||||
use alknet_typedef::tunion;
|
use alknet_typedef::tunion;
|
||||||
|
use alknet_typedef::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -143,7 +143,8 @@ fn write_u32_buffer_too_short_returns_access_error() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn write_string_buffer_too_short_returns_access_error() {
|
fn write_string_buffer_too_short_returns_access_error() {
|
||||||
let mut buffer = vec![0u8; 4];
|
let mut buffer = vec![0u8; 4];
|
||||||
let err = data_access::write_string(&mut buffer, 0, "hello", "name", Endian::Little).unwrap_err();
|
let err =
|
||||||
|
data_access::write_string(&mut buffer, 0, "hello", "name", Endian::Little).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +256,8 @@ fn read_field_discriminator_unknown_value_returns_access_error() -> Result<(), T
|
|||||||
});
|
});
|
||||||
let mut buffer = vec![0u8; 8];
|
let mut buffer = vec![0u8; 8];
|
||||||
buffer[0] = 99;
|
buffer[0] = 99;
|
||||||
let err = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
|
let err =
|
||||||
|
tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -274,7 +276,10 @@ fn layout_builder_missing_var_size_returns_offset_error() {
|
|||||||
match err {
|
match err {
|
||||||
TypedefError::Offset { field_path, reason } => {
|
TypedefError::Offset { field_path, reason } => {
|
||||||
assert_eq!(field_path, "name");
|
assert_eq!(field_path, "name");
|
||||||
assert!(reason.contains("missing variable-length field size"), "reason: {reason}");
|
assert!(
|
||||||
|
reason.contains("missing variable-length field size"),
|
||||||
|
"reason: {reason}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
other => panic!("expected Offset, got {other:?}"),
|
other => panic!("expected Offset, got {other:?}"),
|
||||||
}
|
}
|
||||||
@@ -384,7 +389,8 @@ fn read_string_indirect_data_region_too_short_returns_access_error() {
|
|||||||
let _ = data_access::write_u32(&mut index, 0, 100, "idx.off", Endian::Little);
|
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_access::write_u32(&mut index, 4, 10, "idx.len", Endian::Little);
|
||||||
let data_region = b"too short";
|
let data_region = b"too short";
|
||||||
let err = data_access::read_bytes_indirect(&index, 0, data_region, "blob", Endian::Little).unwrap_err();
|
let err = data_access::read_bytes_indirect(&index, 0, data_region, "blob", Endian::Little)
|
||||||
|
.unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +398,8 @@ fn read_string_indirect_data_region_too_short_returns_access_error() {
|
|||||||
fn read_bytes_indirect_index_too_short_returns_access_error() {
|
fn read_bytes_indirect_index_too_short_returns_access_error() {
|
||||||
let buffer = [0u8; 4];
|
let buffer = [0u8; 4];
|
||||||
let data_region = b"anything";
|
let data_region = b"anything";
|
||||||
let err = data_access::read_bytes_indirect(&buffer, 0, data_region, "blob", Endian::Little).unwrap_err();
|
let err = data_access::read_bytes_indirect(&buffer, 0, data_region, "blob", Endian::Little)
|
||||||
|
.unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +409,7 @@ fn read_string_indirect_invalid_utf8_returns_access_error() {
|
|||||||
let mut index = [0u8; 8];
|
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, 0, 0, "idx.off", Endian::Little);
|
||||||
let _ = data_access::write_u32(&mut index, 4, 3, "idx.len", 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();
|
let err = data_access::read_string_indirect(&index, 0, data_region, "name", Endian::Little)
|
||||||
|
.unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
}
|
}
|
||||||
@@ -8,17 +8,14 @@
|
|||||||
//! reads them back, asserting both the values and (where applicable)
|
//! reads them back, asserting both the values and (where applicable)
|
||||||
//! the byte positions.
|
//! the byte positions.
|
||||||
|
|
||||||
use alknet_typedef::*;
|
|
||||||
use alknet_typedef::data_access;
|
use alknet_typedef::data_access;
|
||||||
use alknet_typedef::tunion;
|
use alknet_typedef::tunion;
|
||||||
|
use alknet_typedef::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn var_sizes(pairs: &[(&str, usize)]) -> HashMap<String, usize> {
|
fn var_sizes(pairs: &[(&str, usize)]) -> HashMap<String, usize> {
|
||||||
pairs
|
pairs.iter().map(|(k, v)| (k.to_string(), *v)).collect()
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (k.to_string(), *v))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -42,7 +39,13 @@ fn fixed_size_round_trip_via_offset_map() -> Result<(), TypedefError> {
|
|||||||
let flag_range = offset_map.get("flag").expect("flag range");
|
let flag_range = offset_map.get("flag").expect("flag range");
|
||||||
data_access::write_u8(&mut buffer, flag_range.start, 1, "flag")?;
|
data_access::write_u8(&mut buffer, flag_range.start, 1, "flag")?;
|
||||||
let count_range = offset_map.get("count").expect("count range");
|
let count_range = offset_map.get("count").expect("count range");
|
||||||
data_access::write_u16(&mut buffer, count_range.start, 1000, "count", Endian::Little)?;
|
data_access::write_u16(
|
||||||
|
&mut buffer,
|
||||||
|
count_range.start,
|
||||||
|
1000,
|
||||||
|
"count",
|
||||||
|
Endian::Little,
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data_access::read_u32(&buffer, id_range.start, "id", Endian::Little)?,
|
data_access::read_u32(&buffer, id_range.start, "id", Endian::Little)?,
|
||||||
@@ -162,12 +165,35 @@ fn nested_struct_round_trip_via_offset_map() -> Result<(), TypedefError> {
|
|||||||
|
|
||||||
let data = b"body-data".to_vec();
|
let data = b"body-data".to_vec();
|
||||||
let mut buffer = vec![0u8; offset_map.total_size() + data.len()];
|
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(
|
||||||
data_access::write_u32(&mut buffer, header_magic.start, 0xCAFEBABE, "header.magic", Endian::Little)?;
|
&mut buffer,
|
||||||
data_access::write_bytes(&mut buffer, payload_prefix.start, &data, "payload", Endian::Little)?;
|
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!(
|
assert_eq!(
|
||||||
data_access::read_u32(&buffer, header_version.start, "header.version", Endian::Little)?,
|
data_access::read_u32(
|
||||||
|
&buffer,
|
||||||
|
header_version.start,
|
||||||
|
"header.version",
|
||||||
|
Endian::Little
|
||||||
|
)?,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -283,13 +309,22 @@ fn alignment_padding_round_trip_u8_then_u32() -> Result<(), TypedefError> {
|
|||||||
|
|
||||||
let mut buffer = vec![0u8; offset_map.total_size()];
|
let mut buffer = vec![0u8; offset_map.total_size()];
|
||||||
data_access::write_u8(&mut buffer, flag_range.start, 0xAB, "flag")?;
|
data_access::write_u8(&mut buffer, flag_range.start, 0xAB, "flag")?;
|
||||||
data_access::write_u32(&mut buffer, id_range.start, 0x01020304, "id", Endian::Little)?;
|
data_access::write_u32(
|
||||||
|
&mut buffer,
|
||||||
|
id_range.start,
|
||||||
|
0x01020304,
|
||||||
|
"id",
|
||||||
|
Endian::Little,
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_eq!(buffer[0], 0xAB);
|
assert_eq!(buffer[0], 0xAB);
|
||||||
assert_eq!(&buffer[1..4], &[0x00, 0x00, 0x00]);
|
assert_eq!(&buffer[1..4], &[0x00, 0x00, 0x00]);
|
||||||
assert_eq!(&buffer[4..8], 0x01020304u32.to_le_bytes());
|
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_u8(&buffer, flag_range.start, "flag")?,
|
||||||
|
0xAB
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data_access::read_u32(&buffer, id_range.start, "id", Endian::Little)?,
|
data_access::read_u32(&buffer, id_range.start, "id", Endian::Little)?,
|
||||||
0x01020304
|
0x01020304
|
||||||
@@ -326,14 +361,23 @@ fn packed_layout_round_trip_via_layout_builder() -> Result<(), TypedefError> {
|
|||||||
let mut buffer = vec![0u8; layout.total_size()];
|
let mut buffer = vec![0u8; layout.total_size()];
|
||||||
data_access::write_u8(&mut buffer, flag_pos.offset, 0xAB, "flag")?;
|
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_u32(&mut buffer, id_pos.offset, 0x01020304, "id", Endian::Little)?;
|
||||||
data_access::write_string(&mut buffer, payload_pos.offset, payload_str, "payload", Endian::Little)?;
|
data_access::write_string(
|
||||||
|
&mut buffer,
|
||||||
|
payload_pos.offset,
|
||||||
|
payload_str,
|
||||||
|
"payload",
|
||||||
|
Endian::Little,
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_eq!(buffer[0], 0xAB);
|
assert_eq!(buffer[0], 0xAB);
|
||||||
assert_eq!(&buffer[1..5], 0x01020304u32.to_le_bytes());
|
assert_eq!(&buffer[1..5], 0x01020304u32.to_le_bytes());
|
||||||
assert_eq!(&buffer[5..9], 10u32.to_le_bytes());
|
assert_eq!(&buffer[5..9], 10u32.to_le_bytes());
|
||||||
assert_eq!(&buffer[9..19], payload_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_u8(&buffer, flag_pos.offset, "flag")?,
|
||||||
|
0xAB
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data_access::read_u32(&buffer, id_pos.offset, "id", Endian::Little)?,
|
data_access::read_u32(&buffer, id_pos.offset, "id", Endian::Little)?,
|
||||||
0x01020304
|
0x01020304
|
||||||
@@ -459,7 +503,9 @@ fn tunion_byte_offset_discriminator_dispatch() -> Result<(), TypedefError> {
|
|||||||
|
|
||||||
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
variant.get("TypeDef:Struct").and_then(serde_json::Value::as_bool),
|
variant
|
||||||
|
.get("TypeDef:Struct")
|
||||||
|
.and_then(serde_json::Value::as_bool),
|
||||||
Some(true)
|
Some(true)
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -486,4 +532,4 @@ fn tunion_byte_offset_discriminator_size_lookup() -> Result<(), TypedefError> {
|
|||||||
assert_eq!(tunion::discriminator_size(&u16_schema)?, 2);
|
assert_eq!(tunion::discriminator_size(&u16_schema)?, 2);
|
||||||
assert_eq!(tunion::discriminator_size(&u32_schema)?, 4);
|
assert_eq!(tunion::discriminator_size(&u32_schema)?, 4);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,9 @@ fn read_byte_discriminator_uint8_dispatches_to_read() -> Result<(), TypedefError
|
|||||||
|
|
||||||
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
variant.get("TypeDef:Struct").and_then(serde_json::Value::as_bool),
|
variant
|
||||||
|
.get("TypeDef:Struct")
|
||||||
|
.and_then(serde_json::Value::as_bool),
|
||||||
Some(true)
|
Some(true)
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -177,7 +179,9 @@ fn read_field_discriminator_string_dispatches_to_read() -> Result<(), TypedefErr
|
|||||||
|
|
||||||
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
let variant = tunion::resolve_variant(&union_schema, &dispatch.key)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
variant.get("TypeDef:Struct").and_then(serde_json::Value::as_bool),
|
variant
|
||||||
|
.get("TypeDef:Struct")
|
||||||
|
.and_then(serde_json::Value::as_bool),
|
||||||
Some(true)
|
Some(true)
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -263,7 +267,8 @@ fn read_field_discriminator_unknown_value_returns_access_error() -> Result<(), T
|
|||||||
});
|
});
|
||||||
let mut buffer = vec![0u8; 8];
|
let mut buffer = vec![0u8; 8];
|
||||||
buffer[0] = 99;
|
buffer[0] = 99;
|
||||||
let err = tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
|
let err =
|
||||||
|
tunion::read_field_discriminator(&buffer, &union_schema, 0, Endian::Little).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -315,4 +320,4 @@ fn parse_discriminator_missing_returns_schema_error() {
|
|||||||
let schema = json!({"TypeDef:Union": true});
|
let schema = json!({"TypeDef:Union": true});
|
||||||
let err = alknet_typedef::parse_discriminator(&schema).unwrap_err();
|
let err = alknet_typedef::parse_discriminator(&schema).unwrap_err();
|
||||||
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
|
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user