Rebrand TypeDef to AlkType in code, keyword strings, and docs

Rename all 19 JSON Schema custom keyword strings from "TypeDef:*"
to "AlkType:*" (e.g., "TypeDef:Struct" -> "AlkType:Struct")
across source, tests, and docs. This is a breaking change to the
schema format itself — existing schemas using the old keywords
must be updated.

Rename the Rust identifiers:
- TypedefEngine -> AlkTypeEngine
- TypedefError -> AlkTypeError
- TypeDefKind -> AlkTypeKind
- TYPEDEF_PREFIX -> ALKTYPE_PREFIX
- get_typedef_kind{,_loose,_loose_enum,_enum} ->
  get_alktype_kind{,_loose,_loose_enum,_enum}

Update error message strings ("unknown TypeDef kind" ->
"unknown AlkType kind"), 11 test function names containing
typedef_kind/to_typedef_error, and doc-comment prose ("TypeDef
kind" -> "AlkType kind", "typedef engine" -> "alktype
engine", "typedef schema" -> "alktype schema"). Fix the broken
docs/architecture/crates/typedef/ path references in source doc
comments to point at docs/architecture/ directly. Rebrand the
typedef:annotation test fixture and the "not-a-typedef" test
string to their alktype equivalents.

Update ~20 generic "typedef" prose references in the architecture
docs ("typedef is the binary struct engine", "use typedef",
"typedef limitation", "replaced by typedef", etc.) to alktype.
Rename TypedefEngine in the ADR-007 code example to AlkTypeEngine.

Preserve as provenance per the prior prose-rebrand decision:
typedef.ts references (external TypeBox source file),
docs/research/alknet-typedef/findings.md research citations,
/workspace/alknet-typedef-poc/ POC path, and the
"alknet-typedef:" research section headers in findings.

Build, 295 tests, and clippy all pass clean.
This commit is contained in:
glm-5.2 committed 2026-08-02 07:05:53 +00:00
1 parent 1cfb3638d1
commit 5e268e8f47
31 files changed
+1851 -1851

No files matched your search

+33 -33
View File
@@ -1,22 +1,22 @@
//! Data access layer: primitive read/write functions for all 17 TypeDef
//! Data access layer: primitive read/write functions for all 17 AlkType
//! kinds with endianness support, bounds checking, and zero-copy access.
//!
//! These are the building blocks used by the layout types ([`crate::offset_map`],
//! [`crate::layout_builder`], [`crate::sequential_reader`]) and the
//! [`crate::engine::TypedefEngine`]. Each function operates on a raw byte
//! buffer at a caller-provided offset and returns a [`TypedefError::Access`]
//! [`crate::engine::AlkTypeEngine`]. Each function operates on a raw byte
//! buffer at a caller-provided offset and returns a [`AlkTypeError::Access`]
//! carrying the field path on bounds or encoding failures.
//!
//! # Conventions
//!
//! - All multi-byte types respect the [`Endian`] parameter passed by the caller.
//! - Bounds checks ensure `buffer.len() >= offset + size`; failures produce
//! [`TypedefError::Access`] with a descriptive `reason`.
//! [`AlkTypeError::Access`] with a descriptive `reason`.
//! - Read functions for variable-length types return slices borrowing from
//! the input buffer — no allocation.
//! - No `unwrap()` / `expect()` on fallible operations.
use crate::error::TypedefError;
use crate::error::AlkTypeError;
use crate::schema::Endian;
const U32_SIZE: usize = 4;
@@ -26,9 +26,9 @@ fn check_bounds(
start: usize,
end: usize,
field_path: &str,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
if end < start || buffer_len < end {
return Err(TypedefError::Access {
return Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!(
"buffer bounds check failed: need bytes [{start}..{end}), buffer has {buffer_len}"
@@ -38,8 +38,8 @@ fn check_bounds(
Ok(())
}
fn access_err(field_path: &str, reason: impl Into<String>) -> TypedefError {
TypedefError::Access {
fn access_err(field_path: &str, reason: impl Into<String>) -> AlkTypeError {
AlkTypeError::Access {
field_path: field_path.to_string(),
reason: reason.into(),
}
@@ -49,7 +49,7 @@ pub(crate) fn read_array<const N: usize>(
buffer: &[u8],
offset: usize,
field_path: &str,
) -> Result<[u8; N], TypedefError> {
) -> Result<[u8; N], AlkTypeError> {
let end = offset.checked_add(N).ok_or_else(|| {
access_err(
field_path,
@@ -79,7 +79,7 @@ pub(crate) fn write_array<const N: usize>(
offset: usize,
bytes: [u8; N],
field_path: &str,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
let end = offset.checked_add(N).ok_or_else(|| {
access_err(
field_path,
@@ -129,9 +129,9 @@ define_read_write_endian!(f64, read_f64, write_f64, 8);
/// Read a `bool` at `offset` from `buffer`.
///
/// `0x00` decodes to `false`, `0x01` decodes to `true`. Any other byte value
/// produces [`TypedefError::Access`] with a reason of the form
/// produces [`AlkTypeError::Access`] with a reason of the form
/// `"invalid boolean byte 0x02 at offset {offset}"`.
pub fn read_bool(buffer: &[u8], offset: usize, field_path: &str) -> Result<bool, TypedefError> {
pub fn read_bool(buffer: &[u8], offset: usize, field_path: &str) -> Result<bool, AlkTypeError> {
let bytes: [u8; 1] = read_array(buffer, offset, field_path)?;
match bytes[0] {
0x00 => Ok(false),
@@ -151,7 +151,7 @@ pub fn write_bool(
offset: usize,
value: bool,
field_path: &str,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
write_array(
buffer,
offset,
@@ -168,7 +168,7 @@ pub fn read_enum(
offset: usize,
field_path: &str,
endian: Endian,
) -> Result<u32, TypedefError> {
) -> Result<u32, AlkTypeError> {
read_u32(buffer, offset, field_path, endian)
}
@@ -179,7 +179,7 @@ pub fn write_enum(
value: u32,
field_path: &str,
endian: Endian,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
write_u32(buffer, offset, value, field_path, endian)
}
@@ -191,13 +191,13 @@ pub fn write_enum(
///
/// Wire format: `[length: u32][UTF-8 bytes]`. The length prefix respects
/// `endian`. Returns a `&'a str` that borrows from the input buffer — no
/// allocation. Invalid UTF-8 produces [`TypedefError::Access`].
/// allocation. Invalid UTF-8 produces [`AlkTypeError::Access`].
pub fn read_string<'a>(
buffer: &'a [u8],
offset: usize,
field_path: &str,
endian: Endian,
) -> Result<&'a str, TypedefError> {
) -> Result<&'a str, AlkTypeError> {
let bytes = read_bytes(buffer, offset, field_path, endian)?;
std::str::from_utf8(bytes).map_err(|e| {
access_err(
@@ -216,7 +216,7 @@ pub fn read_bytes<'a>(
offset: usize,
field_path: &str,
endian: Endian,
) -> Result<&'a [u8], TypedefError> {
) -> Result<&'a [u8], AlkTypeError> {
let len_bytes: [u8; U32_SIZE] = read_array(buffer, offset, field_path)?;
let len = u32_from(len_bytes, endian) as usize;
let data_start = offset.checked_add(U32_SIZE).ok_or_else(|| {
@@ -246,7 +246,7 @@ pub fn write_string(
value: &str,
field_path: &str,
endian: Endian,
) -> Result<usize, TypedefError> {
) -> Result<usize, AlkTypeError> {
write_bytes(buffer, offset, value.as_bytes(), field_path, endian)
}
@@ -260,7 +260,7 @@ pub fn write_bytes(
value: &[u8],
field_path: &str,
endian: Endian,
) -> Result<usize, TypedefError> {
) -> Result<usize, AlkTypeError> {
let data_len = value.len();
let total = U32_SIZE.checked_add(data_len).ok_or_else(|| {
access_err(
@@ -297,14 +297,14 @@ pub fn write_bytes(
/// `{ data_offset: u32, data_length: u32 }` (endian-aware). The actual UTF-8
/// bytes live in `data_region[data_offset..data_offset+data_length]`. Returns
/// a `&'a str` borrowing from `data_region`. Invalid UTF-8 produces
/// [`TypedefError::Access`].
/// [`AlkTypeError::Access`].
pub fn read_string_indirect<'a>(
buffer: &'a [u8],
offset: usize,
data_region: &'a [u8],
field_path: &str,
endian: Endian,
) -> Result<&'a str, TypedefError> {
) -> Result<&'a str, AlkTypeError> {
let bytes = read_bytes_indirect(buffer, offset, data_region, field_path, endian)?;
std::str::from_utf8(bytes).map_err(|e| {
access_err(
@@ -325,7 +325,7 @@ pub fn read_bytes_indirect<'a>(
data_region: &'a [u8],
field_path: &str,
endian: Endian,
) -> Result<&'a [u8], TypedefError> {
) -> Result<&'a [u8], AlkTypeError> {
let struct_end = offset
.checked_add(8)
.ok_or_else(|| access_err(field_path, format!("offset {offset} + 8 overflows usize")))?;
@@ -476,7 +476,7 @@ mod tests {
let buf = [0x02u8];
let err = read_bool(&buf, 0, "f").unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, "f");
assert!(reason.contains("0x02"), "reason: {reason}");
assert!(reason.contains("offset 0"), "reason: {reason}");
@@ -500,7 +500,7 @@ mod tests {
let buf = [0u8; 2];
let err = read_u32(&buf, 0, "header.id", LE).unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, "header.id");
assert!(reason.contains("bounds"), "reason: {reason}");
}
@@ -512,7 +512,7 @@ mod tests {
fn write_bounds_failure_returns_access_error() {
let mut buf = [0u8; 2];
let err = write_u32(&mut buf, 0, 1, "header.id", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
@@ -548,14 +548,14 @@ mod tests {
let mut buf = vec![0u8; 16];
write_bytes(&mut buf, 0, &[0xFF, 0xFE, 0xFD], "name", LE).unwrap();
let err = read_string(&buf, 0, "name", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
fn read_string_bounds_failure_on_prefix() {
let buf = [0u8; 2];
let err = read_string(&buf, 0, "name", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
@@ -565,14 +565,14 @@ mod tests {
let len_bytes = (100u32).to_le_bytes();
buf[0..4].copy_from_slice(&len_bytes);
let err = read_string(&buf, 0, "name", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
fn write_string_bounds_failure() {
let mut buf = vec![0u8; 4];
let err = write_string(&mut buf, 0, "hello", "name", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
@@ -600,7 +600,7 @@ mod tests {
let buf = [0u8; 4];
let data_region = b"anything";
let err = read_bytes_indirect(&buf, 0, data_region, "blob", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
@@ -610,7 +610,7 @@ mod tests {
write_u32(&mut buf, 4, 10, "idx.len", LE).unwrap();
let data_region = b"too short";
let err = read_bytes_indirect(&buf, 0, data_region, "blob", LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
+137 -137
View File
@@ -1,19 +1,19 @@
//! `TypedefEngine` — the compiled form of a schema.
//! `AlkTypeEngine` — the compiled form of a schema.
//!
//! Combines the layout engine (both packed and aligned modes) and the
//! jsonschema validator into a single struct. Built once at schema load
//! time via [`TypedefEngine::compile`]. Used for repeated read/write/
//! time via [`AlkTypeEngine::compile`]. Used for repeated read/write/
//! validate operations at access time.
//!
//! See [validation.md](../../docs/architecture/crates/typedef/validation.md)
//! §"The TypedefEngine struct" and
//! [overview.md](../../docs/architecture/crates/typedef/overview.md).
//! See [validation.md](../../docs/architecture/validation.md)
//! §"The AlkTypeEngine struct" and
//! [overview.md](../../docs/architecture/overview.md).
use crate::data_access;
use crate::error::TypedefError;
use crate::error::AlkTypeError;
use crate::layout_builder::LayoutBuilder;
use crate::offset_map::OffsetMap;
use crate::schema::{self, get_typedef_kind_loose_enum, Endian, TypeDefKind};
use crate::schema::{self, get_alktype_kind_loose_enum, Endian, AlkTypeKind};
use crate::sequential_reader::{FieldValue, SequentialReader};
use crate::validation;
use serde_json::Value;
@@ -32,7 +32,7 @@ pub enum LayoutMode {
///
/// Carries the layout-specific handles needed for read/write access in
/// the selected mode. The consumer chooses the mode at construction time
/// via [`TypedefEngine::compile`]; the engine then exposes only the
/// via [`AlkTypeEngine::compile`]; the engine then exposes only the
/// APIs that make sense for that mode.
#[derive(Debug)]
enum Layout {
@@ -48,26 +48,26 @@ enum Layout {
Aligned { offset_map: OffsetMap },
}
/// The compiled form of a typedef schema. Combines the layout engine
/// The compiled form of a alktype schema. Combines the layout engine
/// (both packed and aligned modes) and the jsonschema validator.
///
/// Built once at schema load time via [`TypedefEngine::compile`].
/// Built once at schema load time via [`AlkTypeEngine::compile`].
/// Used for repeated read/write/validate operations at access time.
///
/// The consumer selects the layout mode at construction time. The engine
/// then exposes mode-appropriate accessors: [`TypedefEngine::offset_map`]
/// for aligned mode, [`TypedefEngine::layout_builder`] and
/// [`TypedefEngine::sequential_reader`] for packed mode. The
/// then exposes mode-appropriate accessors: [`AlkTypeEngine::offset_map`]
/// for aligned mode, [`AlkTypeEngine::layout_builder`] and
/// [`AlkTypeEngine::sequential_reader`] for packed mode. The
/// jsonschema validator is mode-agnostic and always available.
pub struct TypedefEngine {
pub struct AlkTypeEngine {
layout: Layout,
validator: jsonschema::Validator,
endian: Endian,
schema: Value,
}
impl TypedefEngine {
/// Compile a schema into a [`TypedefEngine`].
impl AlkTypeEngine {
/// Compile a schema into a [`AlkTypeEngine`].
///
/// This is the expensive operation — it parses the schema, normalizes
/// `$ref` values, computes the layout, and builds the jsonschema
@@ -79,11 +79,11 @@ impl TypedefEngine {
///
/// # Errors
///
/// Returns [`TypedefError::Schema`] if the schema is malformed or the
/// Returns [`AlkTypeError::Schema`] if the schema is malformed or the
/// underlying layout/validator construction fails. The error is
/// propagated from [`LayoutBuilder::new`], [`SequentialReader::new`],
/// [`OffsetMap::compute`], or [`validation::build_validator`].
pub fn compile(schema: &mut Value, mode: LayoutMode) -> Result<Self, TypedefError> {
pub fn compile(schema: &mut Value, mode: LayoutMode) -> Result<Self, AlkTypeError> {
schema::normalize_refs(schema);
let endian = Endian::from_schema(schema);
let layout = match mode {
@@ -152,12 +152,12 @@ impl TypedefEngine {
/// Validate a JSON value against the schema. The jsonschema validator
/// is already compiled — this is a fast check.
///
/// Returns `Ok(())` if valid, `Err(TypedefError::Validation(...))` if
/// Returns `Ok(())` if valid, `Err(AlkTypeError::Validation(...))` if
/// invalid.
pub fn validate_json(&self, instance: &Value) -> Result<(), TypedefError> {
pub fn validate_json(&self, instance: &Value) -> Result<(), AlkTypeError> {
self.validator
.validate(instance)
.map_err(|e| TypedefError::Validation(e.to_owned()))
.map_err(|e| AlkTypeError::Validation(e.to_owned()))
}
/// Check if a JSON value is valid against the schema.
@@ -173,25 +173,25 @@ impl TypedefEngine {
/// `Bytes`/`Timestamp` fields.
///
/// Returns an error if compiled in packed mode — use
/// [`TypedefEngine::sequential_reader`] for packed mode. Also
/// [`AlkTypeEngine::sequential_reader`] for packed mode. Also
/// returns an error for composite kinds (`Struct`, `Union`, `Array`,
/// `Record`) — those are better handled via the layout-specific APIs.
///
/// # Errors
///
/// - [`TypedefError::Access`] if compiled in packed mode.
/// - [`TypedefError::Offset`] if `field_path` is not in the offset map.
/// - [`TypedefError::Access`] for buffer-too-short or invalid data,
/// - [`AlkTypeError::Access`] if compiled in packed mode.
/// - [`AlkTypeError::Offset`] if `field_path` is not in the offset map.
/// - [`AlkTypeError::Access`] for buffer-too-short or invalid data,
/// propagated from [`crate::data_access`].
pub fn read_field<'a>(
&self,
buffer: &'a [u8],
field_path: &str,
) -> Result<FieldValue<'a>, TypedefError> {
) -> Result<FieldValue<'a>, AlkTypeError> {
let offset_map = match &self.layout {
Layout::Aligned { offset_map } => offset_map,
Layout::Packed { .. } => {
return Err(TypedefError::Access {
return Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "read_field is only available in aligned mode; \
use sequential_reader() for packed mode"
@@ -201,87 +201,87 @@ impl TypedefEngine {
};
let range = offset_map
.get(field_path)
.ok_or_else(|| TypedefError::Offset {
.ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "field not found in offset map".to_string(),
})?;
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(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "field schema not found in schema tree".to_string(),
})?;
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| TypedefError::Offset {
let kind = get_alktype_kind_loose_enum(field_schema).ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "field schema has no TypeDef:* kind".to_string(),
reason: "field schema has no AlkType:* kind".to_string(),
})?;
let endian = self.endian;
match kind {
TypeDefKind::Int8 => {
AlkTypeKind::Int8 => {
let v = data_access::read_i8(buffer, range.start, field_path)?;
Ok(FieldValue::I8(v))
}
TypeDefKind::Int16 => {
AlkTypeKind::Int16 => {
let v = data_access::read_i16(buffer, range.start, field_path, endian)?;
Ok(FieldValue::I16(v))
}
TypeDefKind::Int32 => {
AlkTypeKind::Int32 => {
let v = data_access::read_i32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::I32(v))
}
TypeDefKind::Int64 => {
AlkTypeKind::Int64 => {
let v = data_access::read_i64(buffer, range.start, field_path, endian)?;
Ok(FieldValue::I64(v))
}
TypeDefKind::Uint8 => {
AlkTypeKind::Uint8 => {
let v = data_access::read_u8(buffer, range.start, field_path)?;
Ok(FieldValue::U8(v))
}
TypeDefKind::Uint16 => {
AlkTypeKind::Uint16 => {
let v = data_access::read_u16(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U16(v))
}
TypeDefKind::Uint32 => {
AlkTypeKind::Uint32 => {
let v = data_access::read_u32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U32(v))
}
TypeDefKind::Uint64 => {
AlkTypeKind::Uint64 => {
let v = data_access::read_u64(buffer, range.start, field_path, endian)?;
Ok(FieldValue::U64(v))
}
TypeDefKind::Float32 => {
AlkTypeKind::Float32 => {
let v = data_access::read_f32(buffer, range.start, field_path, endian)?;
Ok(FieldValue::F32(v))
}
TypeDefKind::Float64 => {
AlkTypeKind::Float64 => {
let v = data_access::read_f64(buffer, range.start, field_path, endian)?;
Ok(FieldValue::F64(v))
}
TypeDefKind::Boolean => {
AlkTypeKind::Boolean => {
let v = data_access::read_bool(buffer, range.start, field_path)?;
Ok(FieldValue::Bool(v))
}
TypeDefKind::Enum => {
AlkTypeKind::Enum => {
let v = data_access::read_enum(buffer, range.start, field_path, endian)?;
Ok(FieldValue::Enum(v))
}
TypeDefKind::String => {
AlkTypeKind::String => {
let v = data_access::read_string(buffer, range.start, field_path, endian)?;
Ok(FieldValue::String(v))
}
TypeDefKind::Bytes => {
AlkTypeKind::Bytes => {
let v = data_access::read_bytes(buffer, range.start, field_path, endian)?;
Ok(FieldValue::Bytes(v))
}
TypeDefKind::Timestamp => {
AlkTypeKind::Timestamp => {
let v = data_access::read_string(buffer, range.start, field_path, endian)?;
Ok(FieldValue::String(v))
}
TypeDefKind::Struct => Ok(FieldValue::Struct {
AlkTypeKind::Struct => Ok(FieldValue::Struct {
start: range.start,
end: range.end,
}),
TypeDefKind::Union | TypeDefKind::Array | TypeDefKind::Record => {
Err(TypedefError::Access {
AlkTypeKind::Union | AlkTypeKind::Array | AlkTypeKind::Record => {
Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "read_field does not support composite types; \
use the layout-specific APIs"
@@ -299,25 +299,25 @@ impl TypedefEngine {
/// `Bytes`/`Timestamp` fields.
///
/// Returns an error if compiled in packed mode — use
/// [`TypedefEngine::layout_builder`] for packed mode. Also returns an
/// [`AlkTypeEngine::layout_builder`] for packed mode. Also returns an
/// error for composite kinds (`Struct`, `Union`, `Array`, `Record`).
///
/// # Errors
///
/// - [`TypedefError::Access`] if compiled in packed mode.
/// - [`TypedefError::Offset`] if `field_path` is not in the offset map.
/// - [`TypedefError::Access`] for buffer-too-short or invalid data,
/// - [`AlkTypeError::Access`] if compiled in packed mode.
/// - [`AlkTypeError::Offset`] if `field_path` is not in the offset map.
/// - [`AlkTypeError::Access`] for buffer-too-short or invalid data,
/// propagated from [`crate::data_access`].
pub fn write_field(
&self,
buffer: &mut [u8],
field_path: &str,
value: &FieldValue<'_>,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
let offset_map = match &self.layout {
Layout::Aligned { offset_map } => offset_map,
Layout::Packed { .. } => {
return Err(TypedefError::Access {
return Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "write_field is only available in aligned mode; \
use layout_builder() for packed mode"
@@ -327,7 +327,7 @@ impl TypedefEngine {
};
let range = offset_map
.get(field_path)
.ok_or_else(|| TypedefError::Offset {
.ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "field not found in offset map".to_string(),
})?;
@@ -372,7 +372,7 @@ impl TypedefEngine {
Ok(())
}
FieldValue::Struct { .. } | FieldValue::Union { .. } | FieldValue::Array { .. } => {
Err(TypedefError::Access {
Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: "write_field does not support composite types; \
use the layout-specific APIs"
@@ -383,9 +383,9 @@ impl TypedefEngine {
}
}
impl fmt::Debug for TypedefEngine {
impl fmt::Debug for AlkTypeEngine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedefEngine")
f.debug_struct("AlkTypeEngine")
.field("layout", &self.layout)
.field("validator", &"<jsonschema::Validator>")
.field("endian", &self.endian)
@@ -416,13 +416,13 @@ mod tests {
fn fixed_struct_schema() -> Value {
json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"endian": "little",
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true },
"score": { "TypeDef:Float32": true },
"tag": { "TypeDef:String": true }
"flag": { "AlkType:Uint8": true },
"id": { "AlkType:Uint32": true },
"score": { "AlkType:Float32": true },
"tag": { "AlkType:String": true }
}
})
}
@@ -430,7 +430,7 @@ mod tests {
#[test]
fn compile_aligned_builds_offset_map() {
let mut schema = fixed_struct_schema();
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
assert_eq!(engine.mode(), LayoutMode::Aligned);
assert!(engine.offset_map().is_some());
assert!(engine.layout_builder().is_none());
@@ -440,7 +440,7 @@ mod tests {
#[test]
fn compile_packed_builds_builder_and_reader() {
let mut schema = fixed_struct_schema();
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
assert_eq!(engine.mode(), LayoutMode::Packed);
assert!(engine.layout_builder().is_some());
assert!(engine.sequential_reader().is_some());
@@ -450,18 +450,18 @@ mod tests {
#[test]
fn compile_normalizes_refs() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"child": { "$ref": "Child" }
},
"$defs": {
"Child": {
"TypeDef:Struct": true,
"properties": { "x": { "TypeDef:Uint8": true } }
"AlkType:Struct": true,
"properties": { "x": { "AlkType:Uint8": true } }
}
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
assert_eq!(
engine.schema["properties"]["child"]["$ref"],
json!("#/$defs/Child")
@@ -471,64 +471,64 @@ mod tests {
#[test]
fn endian_parsed_from_schema() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"endian": "big",
"properties": { "id": { "TypeDef:Uint32": true } }
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
assert_eq!(engine.endian(), Endian::Big);
}
#[test]
fn endian_defaults_to_little() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
assert_eq!(engine.endian(), Endian::Little);
}
#[test]
fn validate_json_accepts_valid_instance() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
"id": { "AlkType:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
assert!(engine.validate_json(&json!({"id": 42})).is_ok());
}
#[test]
fn validate_json_rejects_invalid_instance() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
"id": { "AlkType:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let err = engine.validate_json(&json!({"id": -1})).unwrap_err();
assert!(matches!(err, TypedefError::Validation(_)), "got {err:?}");
assert!(matches!(err, AlkTypeError::Validation(_)), "got {err:?}");
}
#[test]
fn is_valid_json_returns_bool() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" }
"id": { "AlkType:Uint32": true, "type": "integer" }
},
"required": ["id"]
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
assert!(engine.is_valid_json(&json!({"id": 42})));
assert!(!engine.is_valid_json(&json!({"id": -1})));
}
@@ -536,14 +536,14 @@ mod tests {
#[test]
fn read_field_aligned_reads_fixed_fields() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"endian": "little",
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true }
"flag": { "AlkType:Uint8": true },
"id": { "AlkType:Uint32": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = vec![0u8; 8];
buf[0] = 0xAB;
buf[4..8].copy_from_slice(&0x01020304u32.to_le_bytes());
@@ -560,12 +560,12 @@ mod tests {
#[test]
fn read_field_aligned_reads_string_length_prefixed() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"name": { "TypeDef:String": true }
"name": { "AlkType:String": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = vec![0u8; 32];
let len_bytes = 5u32.to_le_bytes();
buf[0..4].copy_from_slice(&len_bytes);
@@ -579,55 +579,55 @@ mod tests {
#[test]
fn read_field_returns_access_error_in_packed_mode() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let buf = [0u8; 4];
let err = engine.read_field(&buf, "id").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn read_field_returns_offset_error_for_missing_field() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let buf = [0u8; 4];
let err = engine.read_field(&buf, "missing").unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Offset { .. }), "got {err:?}");
}
#[test]
fn read_field_returns_error_for_composite_types() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"vals": {
"TypeDef:Array": true,
"items": { "TypeDef:Uint32": true }
"AlkType:Array": true,
"items": { "AlkType:Uint32": true }
}
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let buf = [0u8; 8];
let err = engine.read_field(&buf, "vals").unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn write_field_aligned_writes_fixed_fields() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"endian": "little",
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true }
"flag": { "AlkType:Uint8": true },
"id": { "AlkType:Uint32": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = vec![0u8; 8];
engine
.write_field(&mut buf, "flag", &FieldValue::U8(0xAB))
@@ -650,12 +650,12 @@ mod tests {
#[test]
fn write_field_round_trips_string() {
let mut schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"name": { "TypeDef:String": true }
"name": { "AlkType:String": true }
}
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = vec![0u8; 32];
engine
.write_field(&mut buf, "name", &FieldValue::String("hello"))
@@ -669,86 +669,86 @@ mod tests {
#[test]
fn write_field_returns_access_error_in_packed_mode() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).expect("compile");
let mut buf = [0u8; 4];
let err = engine
.write_field(&mut buf, "id", &FieldValue::U32(1))
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn write_field_returns_offset_error_for_missing_field() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = [0u8; 4];
let err = engine
.write_field(&mut buf, "missing", &FieldValue::U32(1))
.unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Offset { .. }), "got {err:?}");
}
#[test]
fn write_field_returns_error_for_composite_value() {
let mut schema = json!({
"TypeDef:Struct": true,
"properties": { "id": { "TypeDef:Uint32": true } }
"AlkType:Struct": true,
"properties": { "id": { "AlkType:Uint32": true } }
});
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let mut buf = [0u8; 8];
let err = engine
.write_field(&mut buf, "id", &FieldValue::Struct { start: 0, end: 4 })
.unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Access { .. }), "got {err:?}");
}
#[test]
fn compile_returns_schema_error_for_invalid_top_level() {
let mut schema = json!({ "type": "object", "properties": {} });
let err = TypedefEngine::compile(&mut schema, LayoutMode::Packed).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
let err = AlkTypeEngine::compile(&mut schema, LayoutMode::Packed).unwrap_err();
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
}
#[test]
fn debug_formats_without_panicking() {
let mut schema = fixed_struct_schema();
let engine = TypedefEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
let s = format!("{engine:?}");
assert!(s.contains("TypedefEngine"));
assert!(s.contains("AlkTypeEngine"));
assert!(s.contains("Aligned"));
}
#[test]
fn lookup_field_schema_walks_dotted_path() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"header": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"version": { "TypeDef:Uint8": true }
"version": { "AlkType:Uint8": true }
}
}
}
});
let node = lookup_field_schema(&schema, "header.version").expect("found");
assert_eq!(node, &json!({ "TypeDef:Uint8": true }));
assert_eq!(node, &json!({ "AlkType:Uint8": true }));
assert!(lookup_field_schema(&schema, "header.missing").is_none());
assert!(lookup_field_schema(&schema, "missing").is_none());
}
#[test]
fn typedef_kind_loose_recognizes_object_form() {
let node = json!({ "TypeDef:String": { "encoding": "offset-indirect" } });
fn alktype_kind_loose_recognizes_object_form() {
let node = json!({ "AlkType:String": { "encoding": "offset-indirect" } });
assert_eq!(
get_typedef_kind_loose_enum(&node),
Some(TypeDefKind::String)
get_alktype_kind_loose_enum(&node),
Some(AlkTypeKind::String)
);
}
}
+12 -12
View File
@@ -1,16 +1,16 @@
//! Error types for the typedef engine.
//! Error types for the alktype engine.
//!
//! Decided in ADR-098: a single `TypedefError` enum covers all error
//! Decided in ADR-098: a single `AlkTypeError` enum covers all error
//! conditions across the engine's three phases (schema parsing, offset
//! computation, read/write) plus validation.
use std::fmt;
/// Errors produced by the typedef engine across all phases.
/// Errors produced by the alktype engine across all phases.
#[derive(Debug)]
pub enum TypedefError {
pub enum AlkTypeError {
/// Schema parsing errors — invalid JSON, missing required keywords,
/// unknown `TypeDef:*` kinds, malformed annotations.
/// unknown `AlkType:*` kinds, malformed annotations.
Schema(String),
/// Offset computation errors — field not found, type not supported
@@ -23,23 +23,23 @@ pub enum TypedefError {
/// Validation errors — delegated to the `jsonschema` crate.
/// The `'static` lifetime is correct: the validator owns its schema
/// reference and lives for the lifetime of the `TypedefEngine`.
/// reference and lives for the lifetime of the `AlkTypeEngine`.
Validation(jsonschema::ValidationError<'static>),
}
impl fmt::Display for TypedefError {
impl fmt::Display for AlkTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypedefError::Schema(msg) => write!(f, "schema error: {msg}"),
TypedefError::Offset { field_path, reason } => {
AlkTypeError::Schema(msg) => write!(f, "schema error: {msg}"),
AlkTypeError::Offset { field_path, reason } => {
write!(f, "offset error at {field_path}: {reason}")
}
TypedefError::Access { field_path, reason } => {
AlkTypeError::Access { field_path, reason } => {
write!(f, "access error at {field_path}: {reason}")
}
TypedefError::Validation(inner) => write!(f, "validation error: {inner}"),
AlkTypeError::Validation(inner) => write!(f, "validation error: {inner}"),
}
}
}
impl std::error::Error for TypedefError {}
impl std::error::Error for AlkTypeError {}
+294 -294
View File
File diff suppressed because it is too large. Load diff
+8 -8
View File
@@ -1,13 +1,13 @@
//! alktype: The binary struct engine.
//!
//! Takes a JSON Schema with `TypeDef:*` custom keywords and produces
//! Takes a JSON Schema with `AlkType:*` custom keywords and produces
//! an offset map, read/write functions, and validation — all driven
//! by the schema. The schema is the format definition; the engine is
//! generic.
//!
//! ## Architecture
//!
//! - **Schema layer** ([`schema`]): TypeDef kind detection, annotation
//! - **Schema layer** ([`schema`]): AlkType kind detection, annotation
//! parsing, `$ref` normalization, endianness.
//! - **Layout engine** ([`offset_map`], [`layout_builder`],
//! [`sequential_reader`]): Two layout modes — aligned static for
@@ -17,8 +17,8 @@
//! - **TUnion dispatch** ([`tunion`]): Byte-offset and field-name
//! discriminator dispatch.
//! - **Validation** ([`validation`]): Custom keyword validators for all
//! 17 `TypeDef:*` kinds, delegated to the `jsonschema` crate.
//! - **Engine** ([`engine`]): `TypedefEngine` — the compiled form of a
//! 17 `AlkType:*` kinds, delegated to the `jsonschema` crate.
//! - **Engine** ([`engine`]): `AlkTypeEngine` — the compiled form of a
//! schema, combining layout and validation.
#[macro_use]
@@ -33,14 +33,14 @@ pub mod sequential_reader;
pub mod tunion;
pub mod validation;
pub use engine::{LayoutMode, TypedefEngine};
pub use error::TypedefError;
pub use engine::{LayoutMode, AlkTypeEngine};
pub use error::AlkTypeError;
pub use layout_builder::{FieldPosition, LayoutBuilder, PackedLayout};
pub use offset_map::{ByteRange, OffsetMap};
pub use schema::{
get_typedef_kind_loose, get_typedef_kind_loose_enum, normalize_refs, parse_align,
get_alktype_kind_loose, get_alktype_kind_loose_enum, normalize_refs, parse_align,
parse_discriminator, parse_encoding, parse_endian, parse_max_length, resolve_ref,
resolve_ref_or_inline, DiscriminatorKind, Endian, TypeDefKind, VariableEncoding,
resolve_ref_or_inline, DiscriminatorKind, Endian, AlkTypeKind, VariableEncoding,
};
pub use sequential_reader::{FieldValue, SequentialReader};
pub use tunion::UnionDispatch;
+5 -5
View File
@@ -1,4 +1,4 @@
//! Macros for generating repetitive code across the 17 TypeDef kinds.
//! Macros for generating repetitive code across the 17 AlkType kinds.
//!
//! These macros eliminate boilerplate in validation, data access, and
//! dispatch. Each macro takes a compact specification and generates the
@@ -187,7 +187,7 @@ macro_rules! define_read_write_endian {
offset: usize,
field_path: &str,
endian: $crate::Endian,
) -> Result<$rust_ty, $crate::TypedefError> {
) -> Result<$rust_ty, $crate::AlkTypeError> {
let bytes: [u8; $size] = $crate::data_access::read_array(buffer, offset, field_path)?;
Ok(match endian {
$crate::Endian::Little => <$rust_ty>::from_le_bytes(bytes),
@@ -206,7 +206,7 @@ macro_rules! define_read_write_endian {
value: $rust_ty,
field_path: &str,
endian: $crate::Endian,
) -> Result<(), $crate::TypedefError> {
) -> Result<(), $crate::AlkTypeError> {
let bytes = match endian {
$crate::Endian::Little => value.to_le_bytes(),
$crate::Endian::Big => value.to_be_bytes(),
@@ -229,7 +229,7 @@ macro_rules! define_read_write_ne {
buffer: &[u8],
offset: usize,
field_path: &str,
) -> Result<$rust_ty, $crate::TypedefError> {
) -> Result<$rust_ty, $crate::AlkTypeError> {
let bytes: [u8; $size] = $crate::data_access::read_array(buffer, offset, field_path)?;
Ok($read_expr(bytes))
}
@@ -244,7 +244,7 @@ macro_rules! define_read_write_ne {
offset: usize,
value: $rust_ty,
field_path: &str,
) -> Result<(), $crate::TypedefError> {
) -> Result<(), $crate::AlkTypeError> {
$crate::data_access::write_array(buffer, offset, value.to_ne_bytes(), field_path)
}
};
+127 -127
View File
@@ -11,10 +11,10 @@
//! to satisfy the field's alignment requirement (natural alignment by
//! default, overridable via the `"align"` annotation).
use crate::error::TypedefError;
use crate::error::AlkTypeError;
use crate::schema::{
get_typedef_kind, get_typedef_kind_loose_enum, parse_align, parse_encoding,
parse_max_length, resolve_ref_or_inline, TypeDefKind, VariableEncoding,
get_alktype_kind, get_alktype_kind_loose_enum, parse_align, parse_encoding,
parse_max_length, resolve_ref_or_inline, AlkTypeKind, VariableEncoding,
};
use serde_json::Value;
@@ -67,25 +67,25 @@ impl OffsetMap {
///
/// Walks the schema recursively, computing byte positions for each
/// field based on type sizes, field order, and alignment. The
/// top-level schema must be a `TypeDef:Struct`.
/// top-level schema must be a `AlkType:Struct`.
///
/// # Errors
///
/// Returns [`TypedefError::Schema`] if the top-level schema is not a
/// `TypeDef:Struct` or has no `TypeDef:*` kind, or if the schema is
/// Returns [`AlkTypeError::Schema`] if the top-level schema is not a
/// `AlkType:Struct` or has no `AlkType:*` kind, or if the schema is
/// malformed (missing `properties`, unknown kind, etc.).
///
/// Returns [`TypedefError::Offset`] for unsupported type combinations
/// Returns [`AlkTypeError::Offset`] for unsupported type combinations
/// encountered during the walk.
pub fn compute(schema: &Value) -> Result<Self, TypedefError> {
let kind = get_typedef_kind(schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
pub fn compute(schema: &Value) -> Result<Self, AlkTypeError> {
let kind = get_alktype_kind(schema)
.and_then(|s| s.parse::<AlkTypeKind>().ok())
.ok_or_else(|| {
TypedefError::Schema("top-level schema has no TypeDef:* kind".to_string())
AlkTypeError::Schema("top-level schema has no AlkType:* kind".to_string())
})?;
if kind != TypeDefKind::Struct {
return Err(TypedefError::Schema(format!(
"OffsetMap::compute requires a TypeDef:Struct at the top level, got {kind}"
if kind != AlkTypeKind::Struct {
return Err(AlkTypeError::Schema(format!(
"OffsetMap::compute requires a AlkType:Struct at the top level, got {kind}"
)));
}
let mut ctx = ComputeCtx {
@@ -145,7 +145,7 @@ struct FieldLayout {
}
impl<'a> ComputeCtx<'a> {
/// Recurse into a `TypeDef:Struct`, appending `(field_path, ByteRange)`
/// Recurse into a `AlkType:Struct`, appending `(field_path, ByteRange)`
/// pairs to `self.fields` and advancing `self.offset`.
///
/// Returns `(total_size, alignment)` where `total_size` includes
@@ -163,15 +163,15 @@ impl<'a> ComputeCtx<'a> {
struct_schema: &Value,
prefix: &str,
parent_struct_align: usize,
) -> Result<(usize, usize), TypedefError> {
) -> Result<(usize, usize), AlkTypeError> {
let obj = struct_schema
.as_object()
.ok_or_else(|| TypedefError::Schema("struct schema is not an object".to_string()))?;
.ok_or_else(|| AlkTypeError::Schema("struct schema is not an object".to_string()))?;
let properties = obj
.get("properties")
.and_then(|v| v.as_object())
.ok_or_else(|| {
TypedefError::Schema("struct schema has no 'properties' object".to_string())
AlkTypeError::Schema("struct schema has no 'properties' object".to_string())
})?;
let struct_default_align = parse_align(struct_schema).unwrap_or(parent_struct_align);
@@ -194,13 +194,13 @@ impl<'a> ComputeCtx<'a> {
// data_access::write_string writes prefix+data inline — clobbering
// subsequent fields. Only allowed as the last field in the struct.
if i < field_count - 1 {
if let Some(kind) = get_typedef_kind_loose_enum(field_schema) {
if let Some(kind) = get_alktype_kind_loose_enum(field_schema) {
if kind.is_variable_length() {
let keyword_value = field_schema
.as_object()
.and_then(|o| {
o.keys()
.find(|k| k.starts_with("TypeDef:"))
.find(|k| k.starts_with("AlkType:"))
.and_then(|k| o.get(k))
})
.cloned()
@@ -210,7 +210,7 @@ impl<'a> ComputeCtx<'a> {
let is_inline_length_prefixed =
encoding == VariableEncoding::LengthPrefixed && max_length.is_none();
if is_inline_length_prefixed {
return Err(TypedefError::Offset {
return Err(AlkTypeError::Offset {
field_path: field_path.clone(),
reason: format!(
"non-final inline length-prefixed variable field \
@@ -244,17 +244,17 @@ impl<'a> ComputeCtx<'a> {
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
let kind = get_typedef_kind_loose_enum(field_schema).ok_or_else(|| TypedefError::Offset {
) -> Result<FieldLayout, AlkTypeError> {
let kind = get_alktype_kind_loose_enum(field_schema).ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "field schema has no TypeDef:* kind".to_string(),
reason: "field schema has no AlkType:* kind".to_string(),
})?;
match kind {
TypeDefKind::Struct => {
AlkTypeKind::Struct => {
self.compute_struct_field(field_schema, field_path, struct_default_align)
}
TypeDefKind::Union => Err(TypedefError::Offset {
AlkTypeKind::Union => Err(AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "TUnion is not supported in aligned static mode (ADR-102). \
Unions are the protocol dispatch pattern — use packed sequential \
@@ -262,31 +262,31 @@ impl<'a> ComputeCtx<'a> {
a struct with an explicit discriminator field."
.to_string(),
}),
TypeDefKind::Array => {
AlkTypeKind::Array => {
self.compute_array_field(field_schema, field_path, struct_default_align)
}
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Record
| TypeDefKind::Timestamp => {
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Record
| AlkTypeKind::Timestamp => {
self.compute_variable_field(field_schema, field_path, struct_default_align)
}
k if k.is_fixed_size() => {
self.compute_fixed_field(k, field_schema, field_path, struct_default_align)
}
_ => unreachable!("all TypeDefKind variants are covered above"),
_ => unreachable!("all AlkTypeKind variants are covered above"),
}
}
/// Compute the layout for a fixed-size primitive field.
fn compute_fixed_field(
&mut self,
kind: TypeDefKind,
kind: AlkTypeKind,
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
let size = kind.type_size().ok_or_else(|| TypedefError::Offset {
) -> Result<FieldLayout, AlkTypeError> {
let size = kind.type_size().ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: format!("type_size returned None for fixed kind {kind}"),
})?;
@@ -299,7 +299,7 @@ impl<'a> ComputeCtx<'a> {
Ok(FieldLayout { align })
}
/// Compute the layout for a nested `TypeDef:Struct` field.
/// Compute the layout for a nested `AlkType:Struct` field.
///
/// Probes the nested struct's layout at a temporary offset of 0 to
/// determine its total size and alignment, aligns the parent offset,
@@ -309,7 +309,7 @@ impl<'a> ComputeCtx<'a> {
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
) -> Result<FieldLayout, AlkTypeError> {
let inner_parent_align = parse_align(field_schema).unwrap_or(struct_default_align);
let mut probe = ComputeCtx {
root: self.root,
@@ -335,37 +335,37 @@ impl<'a> ComputeCtx<'a> {
Ok(FieldLayout { align })
}
/// Compute the layout for a `TypeDef:Array` field.
/// Compute the layout for a `AlkType:Array` field.
fn compute_array_field(
&mut self,
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
) -> Result<FieldLayout, AlkTypeError> {
let obj = field_schema
.as_object()
.ok_or_else(|| TypedefError::Offset {
.ok_or_else(|| AlkTypeError::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(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "TArray is missing 'items'".to_string(),
})?;
let element_schema =
resolve_ref_or_inline(items, self.root).ok_or_else(|| TypedefError::Offset {
resolve_ref_or_inline(items, self.root).ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "could not resolve TArray items schema".to_string(),
})?;
let elem_kind = get_typedef_kind(element_schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
.ok_or_else(|| TypedefError::Offset {
let elem_kind = get_alktype_kind(element_schema)
.and_then(|s| s.parse::<AlkTypeKind>().ok())
.ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: "TArray element schema has no TypeDef:* kind".to_string(),
reason: "TArray element schema has no AlkType:* kind".to_string(),
})?;
if !elem_kind.is_fixed_size() {
return Err(TypedefError::Offset {
return Err(AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: format!(
"TArray of variable-length element kind {elem_kind} is not supported (OQ-069)"
@@ -373,7 +373,7 @@ impl<'a> ComputeCtx<'a> {
});
}
let elem_size = elem_kind.type_size().ok_or_else(|| TypedefError::Offset {
let elem_size = elem_kind.type_size().ok_or_else(|| AlkTypeError::Offset {
field_path: field_path.to_string(),
reason: format!("element kind {elem_kind} has no fixed size"),
})?;
@@ -431,12 +431,12 @@ impl<'a> ComputeCtx<'a> {
field_schema: &Value,
field_path: &str,
struct_default_align: usize,
) -> Result<FieldLayout, TypedefError> {
) -> Result<FieldLayout, AlkTypeError> {
let keyword_value = field_schema
.as_object()
.and_then(|o| {
o.keys()
.find(|k| k.starts_with("TypeDef:"))
.find(|k| k.starts_with("AlkType:"))
.and_then(|k| o.get(k))
})
.cloned()
@@ -510,10 +510,10 @@ mod tests {
#[test]
fn simple_fixed_fields_natural_alignment() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"flag": { "TypeDef:Uint8": true },
"id": { "TypeDef:Uint32": true }
"flag": { "AlkType:Uint8": true },
"id": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -525,10 +525,10 @@ mod tests {
#[test]
fn u8_then_u32_three_bytes_padding() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"a": { "TypeDef:Uint8": true },
"b": { "TypeDef:Uint32": true }
"a": { "AlkType:Uint8": true },
"b": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -539,16 +539,16 @@ mod tests {
#[test]
fn nested_struct_dotted_paths() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"header": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"magic": { "TypeDef:Uint32": true },
"version": { "TypeDef:Uint8": true }
"magic": { "AlkType:Uint32": true },
"version": { "AlkType:Uint8": true }
}
},
"body": { "TypeDef:Uint32": true }
"body": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -564,11 +564,11 @@ mod tests {
#[test]
fn array_fixed_count_element_offsets() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"vals": {
"TypeDef:Array": true,
"items": { "TypeDef:Uint32": true },
"AlkType:Array": true,
"items": { "AlkType:Uint32": true },
"minItems": 3,
"maxItems": 3
}
@@ -584,11 +584,11 @@ mod tests {
#[test]
fn array_variable_count_length_prefix() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"vals": {
"TypeDef:Array": true,
"items": { "TypeDef:Uint32": true }
"AlkType:Array": true,
"items": { "AlkType:Uint32": true }
}
}
});
@@ -600,10 +600,10 @@ mod tests {
#[test]
fn variable_string_length_prefix_at_known_offset() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true },
"name": { "TypeDef:String": true }
"id": { "AlkType:Uint32": true },
"name": { "AlkType:String": true }
}
});
let m = map(&schema);
@@ -615,10 +615,10 @@ mod tests {
#[test]
fn variable_string_max_length_reservation() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true },
"name": { "TypeDef:String": true, "maxLength": 256 }
"id": { "AlkType:Uint32": true },
"name": { "AlkType:String": true, "maxLength": 256 }
}
});
let m = map(&schema);
@@ -630,10 +630,10 @@ mod tests {
#[test]
fn variable_string_offset_indirect_eight_bytes() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true },
"blob": { "TypeDef:String": { "encoding": "offset-indirect" } }
"id": { "AlkType:Uint32": true },
"blob": { "AlkType:String": { "encoding": "offset-indirect" } }
}
});
let m = map(&schema);
@@ -645,14 +645,14 @@ mod tests {
#[test]
fn union_byte_discriminator_rejected_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"payload": {
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {
"kind": "byte",
"offset": 0,
"type": "TypeDef:Uint8"
"type": "AlkType:Uint8"
},
"mapping": {
"5": { "$ref": "#/$defs/Read" },
@@ -662,26 +662,26 @@ mod tests {
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true }
"handle": { "AlkType:Uint32": true },
"length": { "AlkType:Uint32": true }
}
},
"Write": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true },
"data": { "TypeDef:Uint32": true }
"handle": { "AlkType:Uint32": true },
"length": { "AlkType:Uint32": true },
"data": { "AlkType:Uint32": true }
}
}
}
});
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Offset { .. }), "got {err:?}");
let reason = match err {
TypedefError::Offset { reason, .. } => reason,
AlkTypeError::Offset { reason, .. } => reason,
_ => unreachable!(),
};
assert!(reason.contains("ADR-102"), "reason: {reason}");
@@ -690,10 +690,10 @@ mod tests {
#[test]
fn union_field_name_discriminator_rejected_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"event": {
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": { "kind": "field", "name": "type" },
"mapping": {
"read": { "$ref": "#/$defs/Read" },
@@ -703,38 +703,38 @@ mod tests {
},
"$defs": {
"Read": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"type": { "TypeDef:Uint8": true },
"handle": { "TypeDef:Uint32": true }
"type": { "AlkType:Uint8": true },
"handle": { "AlkType:Uint32": true }
}
},
"Write": {
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"type": { "TypeDef:Uint8": true },
"handle": { "TypeDef:Uint32": true },
"length": { "TypeDef:Uint32": true }
"type": { "AlkType:Uint8": true },
"handle": { "AlkType:Uint32": true },
"length": { "AlkType:Uint32": true }
}
}
}
});
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Offset { .. }), "got {err:?}");
assert!(matches!(err, AlkTypeError::Offset { .. }), "got {err:?}");
}
#[test]
fn non_final_inline_string_rejected_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"name": { "TypeDef:String": true },
"id": { "TypeDef:Uint32": true }
"name": { "AlkType:String": true },
"id": { "AlkType:Uint32": true }
}
});
let err = OffsetMap::compute(&schema).unwrap_err();
match err {
TypedefError::Offset { field_path, reason } => {
AlkTypeError::Offset { field_path, reason } => {
assert_eq!(field_path, "name");
assert!(reason.contains("ADR-100"), "reason: {reason}");
}
@@ -745,10 +745,10 @@ mod tests {
#[test]
fn final_inline_string_allowed_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"id": { "TypeDef:Uint32": true },
"name": { "TypeDef:String": true }
"id": { "AlkType:Uint32": true },
"name": { "AlkType:String": true }
}
});
let m = map(&schema);
@@ -759,10 +759,10 @@ mod tests {
#[test]
fn non_final_maxlength_string_allowed_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"name": { "TypeDef:String": true, "maxLength": 256 },
"id": { "TypeDef:Uint32": true }
"name": { "AlkType:String": true, "maxLength": 256 },
"id": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -773,10 +773,10 @@ mod tests {
#[test]
fn non_final_offset_indirect_string_allowed_in_aligned_mode() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"blob": { "TypeDef:String": { "encoding": "offset-indirect" } },
"id": { "TypeDef:Uint32": true }
"blob": { "AlkType:String": { "encoding": "offset-indirect" } },
"id": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -787,10 +787,10 @@ mod tests {
#[test]
fn struct_level_align_rounds_up_total() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"align": 16,
"properties": {
"flag": { "TypeDef:Uint8": true }
"flag": { "AlkType:Uint8": true }
}
});
let m = map(&schema);
@@ -801,12 +801,12 @@ mod tests {
#[test]
fn field_level_align_overrides_struct_default() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"align": 1,
"properties": {
"tag": { "TypeDef:Uint8": true },
"flag": { "TypeDef:Uint8": true, "align": 16 },
"id": { "TypeDef:Uint32": true }
"tag": { "AlkType:Uint8": true },
"flag": { "AlkType:Uint8": true, "align": 16 },
"id": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -819,11 +819,11 @@ mod tests {
#[test]
fn field_align_smaller_than_struct_default() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"align": 8,
"properties": {
"a": { "TypeDef:Uint8": true },
"b": { "TypeDef:Uint32": true, "align": 1 }
"a": { "AlkType:Uint8": true },
"b": { "AlkType:Uint32": true, "align": 1 }
}
});
let m = map(&schema);
@@ -835,10 +835,10 @@ mod tests {
#[test]
fn iter_returns_all_paths_in_order() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"properties": {
"a": { "TypeDef:Uint8": true },
"b": { "TypeDef:Uint32": true }
"a": { "AlkType:Uint8": true },
"b": { "AlkType:Uint32": true }
}
});
let m = map(&schema);
@@ -849,16 +849,16 @@ mod tests {
#[test]
fn compute_rejects_non_struct_top_level() {
let schema =
json!({ "TypeDef:Union": true, "discriminator": { "kind": "byte" }, "mapping": {} });
json!({ "AlkType:Union": true, "discriminator": { "kind": "byte" }, "mapping": {} });
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn compute_rejects_missing_typedef_kind() {
fn compute_rejects_missing_alktype_kind() {
let schema = json!({ "type": "object", "properties": {} });
let err = OffsetMap::compute(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
+222 -222
View File
@@ -1,36 +1,36 @@
//! Schema layer: TypeDef kind detection, annotation parsing, `$ref`
//! Schema layer: AlkType kind detection, annotation parsing, `$ref`
//! normalization, and the `Endian` enum.
//!
//! Per ADR-097 and the schema-layer spec. This module provides the
//! foundational types and functions that every other module depends on:
//! `TypeDef:*` kind detection, fixed byte-size lookups, natural alignment,
//! `AlkType:*` kind detection, fixed byte-size lookups, natural alignment,
//! schema annotation parsing (`encoding`, `align`, `maxLength`, `endian`),
//! TUnion discriminator parsing, and `$ref` normalization from TypeBox
//! bare-name refs to JSON Pointer refs.
use crate::error::TypedefError;
use crate::error::AlkTypeError;
use serde_json::Value;
use std::fmt;
use std::str::FromStr;
const TYPEDEF_PREFIX: &str = "TypeDef:";
const ALKTYPE_PREFIX: &str = "AlkType:";
pub(crate) const U32_SIZE: usize = 4;
pub(crate) const DISCRIMINATOR_PATH: &str = "__discriminator";
const BYTE_DISCRIMINATOR_TYPES: &[TypeDefKind] = &[
TypeDefKind::Uint8,
TypeDefKind::Uint16,
TypeDefKind::Uint32,
const BYTE_DISCRIMINATOR_TYPES: &[AlkTypeKind] = &[
AlkTypeKind::Uint8,
AlkTypeKind::Uint16,
AlkTypeKind::Uint32,
];
/// The 19 `TypeDef:*` kinds recognized by the engine.
/// The 19 `AlkType:*` kinds recognized by the engine.
///
/// Each variant corresponds to a `TypeDef:<name>` JSON Schema keyword.
/// Each variant corresponds to a `AlkType:<name>` JSON Schema keyword.
/// The enum provides compile-time exhaustiveness checking and integer
/// discriminant dispatch (jump table) instead of string comparison.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeDefKind {
pub enum AlkTypeKind {
Int8,
Int16,
Int32,
@@ -52,48 +52,48 @@ pub enum TypeDefKind {
Timestamp,
}
impl TypeDefKind {
/// The JSON Schema keyword string, e.g. `"TypeDef:Int8"`.
impl AlkTypeKind {
/// The JSON Schema keyword string, e.g. `"AlkType:Int8"`.
pub fn as_str(self) -> &'static str {
match self {
TypeDefKind::Int8 => "TypeDef:Int8",
TypeDefKind::Int16 => "TypeDef:Int16",
TypeDefKind::Int32 => "TypeDef:Int32",
TypeDefKind::Int64 => "TypeDef:Int64",
TypeDefKind::Uint8 => "TypeDef:Uint8",
TypeDefKind::Uint16 => "TypeDef:Uint16",
TypeDefKind::Uint32 => "TypeDef:Uint32",
TypeDefKind::Uint64 => "TypeDef:Uint64",
TypeDefKind::Float32 => "TypeDef:Float32",
TypeDefKind::Float64 => "TypeDef:Float64",
TypeDefKind::Boolean => "TypeDef:Boolean",
TypeDefKind::Enum => "TypeDef:Enum",
TypeDefKind::String => "TypeDef:String",
TypeDefKind::Bytes => "TypeDef:Bytes",
TypeDefKind::Struct => "TypeDef:Struct",
TypeDefKind::Union => "TypeDef:Union",
TypeDefKind::Array => "TypeDef:Array",
TypeDefKind::Record => "TypeDef:Record",
TypeDefKind::Timestamp => "TypeDef:Timestamp",
AlkTypeKind::Int8 => "AlkType:Int8",
AlkTypeKind::Int16 => "AlkType:Int16",
AlkTypeKind::Int32 => "AlkType:Int32",
AlkTypeKind::Int64 => "AlkType:Int64",
AlkTypeKind::Uint8 => "AlkType:Uint8",
AlkTypeKind::Uint16 => "AlkType:Uint16",
AlkTypeKind::Uint32 => "AlkType:Uint32",
AlkTypeKind::Uint64 => "AlkType:Uint64",
AlkTypeKind::Float32 => "AlkType:Float32",
AlkTypeKind::Float64 => "AlkType:Float64",
AlkTypeKind::Boolean => "AlkType:Boolean",
AlkTypeKind::Enum => "AlkType:Enum",
AlkTypeKind::String => "AlkType:String",
AlkTypeKind::Bytes => "AlkType:Bytes",
AlkTypeKind::Struct => "AlkType:Struct",
AlkTypeKind::Union => "AlkType:Union",
AlkTypeKind::Array => "AlkType:Array",
AlkTypeKind::Record => "AlkType:Record",
AlkTypeKind::Timestamp => "AlkType:Timestamp",
}
}
/// Fixed byte size, or `None` for variable-size / composite kinds.
pub fn type_size(self) -> Option<usize> {
match self {
TypeDefKind::Float32 | TypeDefKind::Int32 | TypeDefKind::Uint32 | TypeDefKind::Enum => {
AlkTypeKind::Float32 | AlkTypeKind::Int32 | AlkTypeKind::Uint32 | AlkTypeKind::Enum => {
Some(4)
}
TypeDefKind::Float64 | TypeDefKind::Int64 | TypeDefKind::Uint64 => Some(8),
TypeDefKind::Int8 | TypeDefKind::Uint8 | TypeDefKind::Boolean => Some(1),
TypeDefKind::Int16 | TypeDefKind::Uint16 => Some(2),
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Struct
| TypeDefKind::Union
| TypeDefKind::Array
| TypeDefKind::Record
| TypeDefKind::Timestamp => None,
AlkTypeKind::Float64 | AlkTypeKind::Int64 | AlkTypeKind::Uint64 => Some(8),
AlkTypeKind::Int8 | AlkTypeKind::Uint8 | AlkTypeKind::Boolean => Some(1),
AlkTypeKind::Int16 | AlkTypeKind::Uint16 => Some(2),
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Struct
| AlkTypeKind::Union
| AlkTypeKind::Array
| AlkTypeKind::Record
| AlkTypeKind::Timestamp => None,
}
}
@@ -102,18 +102,18 @@ impl TypeDefKind {
/// composites.
pub fn natural_alignment(self) -> usize {
match self {
TypeDefKind::Int8 | TypeDefKind::Uint8 | TypeDefKind::Boolean => 1,
TypeDefKind::Int16 | TypeDefKind::Uint16 => 2,
TypeDefKind::Int32
| TypeDefKind::Uint32
| TypeDefKind::Float32
| TypeDefKind::Enum => 4,
TypeDefKind::Float64 | TypeDefKind::Int64 | TypeDefKind::Uint64 => 8,
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Record
| TypeDefKind::Timestamp => 4,
TypeDefKind::Struct | TypeDefKind::Union | TypeDefKind::Array => 1,
AlkTypeKind::Int8 | AlkTypeKind::Uint8 | AlkTypeKind::Boolean => 1,
AlkTypeKind::Int16 | AlkTypeKind::Uint16 => 2,
AlkTypeKind::Int32
| AlkTypeKind::Uint32
| AlkTypeKind::Float32
| AlkTypeKind::Enum => 4,
AlkTypeKind::Float64 | AlkTypeKind::Int64 | AlkTypeKind::Uint64 => 8,
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Record
| AlkTypeKind::Timestamp => 4,
AlkTypeKind::Struct | AlkTypeKind::Union | AlkTypeKind::Array => 1,
}
}
@@ -121,18 +121,18 @@ impl TypeDefKind {
pub fn is_fixed_size(self) -> bool {
matches!(
self,
TypeDefKind::Float32
| TypeDefKind::Float64
| TypeDefKind::Int8
| TypeDefKind::Int16
| TypeDefKind::Int32
| TypeDefKind::Int64
| TypeDefKind::Uint8
| TypeDefKind::Uint16
| TypeDefKind::Uint32
| TypeDefKind::Uint64
| TypeDefKind::Boolean
| TypeDefKind::Enum
AlkTypeKind::Float32
| AlkTypeKind::Float64
| AlkTypeKind::Int8
| AlkTypeKind::Int16
| AlkTypeKind::Int32
| AlkTypeKind::Int64
| AlkTypeKind::Uint8
| AlkTypeKind::Uint16
| AlkTypeKind::Uint32
| AlkTypeKind::Uint64
| AlkTypeKind::Boolean
| AlkTypeKind::Enum
)
}
@@ -140,18 +140,18 @@ impl TypeDefKind {
pub fn needs_endian(self) -> bool {
matches!(
self,
TypeDefKind::Int16
| TypeDefKind::Int32
| TypeDefKind::Int64
| TypeDefKind::Uint16
| TypeDefKind::Uint32
| TypeDefKind::Uint64
| TypeDefKind::Float32
| TypeDefKind::Float64
| TypeDefKind::Enum
| TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Timestamp
AlkTypeKind::Int16
| AlkTypeKind::Int32
| AlkTypeKind::Int64
| AlkTypeKind::Uint16
| AlkTypeKind::Uint32
| AlkTypeKind::Uint64
| AlkTypeKind::Float32
| AlkTypeKind::Float64
| AlkTypeKind::Enum
| AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Timestamp
)
}
@@ -159,7 +159,7 @@ impl TypeDefKind {
pub fn is_composite(self) -> bool {
matches!(
self,
TypeDefKind::Struct | TypeDefKind::Union | TypeDefKind::Array | TypeDefKind::Record
AlkTypeKind::Struct | AlkTypeKind::Union | AlkTypeKind::Array | AlkTypeKind::Record
)
}
@@ -167,72 +167,72 @@ impl TypeDefKind {
pub fn is_variable_length(self) -> bool {
matches!(
self,
TypeDefKind::String
| TypeDefKind::Bytes
| TypeDefKind::Timestamp
| TypeDefKind::Record
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Timestamp
| AlkTypeKind::Record
)
}
}
impl fmt::Display for TypeDefKind {
impl fmt::Display for AlkTypeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TypeDefKind {
type Err = TypedefError;
impl FromStr for AlkTypeKind {
type Err = AlkTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"TypeDef:Int8" => Ok(TypeDefKind::Int8),
"TypeDef:Int16" => Ok(TypeDefKind::Int16),
"TypeDef:Int32" => Ok(TypeDefKind::Int32),
"TypeDef:Int64" => Ok(TypeDefKind::Int64),
"TypeDef:Uint8" => Ok(TypeDefKind::Uint8),
"TypeDef:Uint16" => Ok(TypeDefKind::Uint16),
"TypeDef:Uint32" => Ok(TypeDefKind::Uint32),
"TypeDef:Uint64" => Ok(TypeDefKind::Uint64),
"TypeDef:Float32" => Ok(TypeDefKind::Float32),
"TypeDef:Float64" => Ok(TypeDefKind::Float64),
"TypeDef:Boolean" => Ok(TypeDefKind::Boolean),
"TypeDef:Enum" => Ok(TypeDefKind::Enum),
"TypeDef:String" => Ok(TypeDefKind::String),
"TypeDef:Bytes" => Ok(TypeDefKind::Bytes),
"TypeDef:Struct" => Ok(TypeDefKind::Struct),
"TypeDef:Union" => Ok(TypeDefKind::Union),
"TypeDef:Array" => Ok(TypeDefKind::Array),
"TypeDef:Record" => Ok(TypeDefKind::Record),
"TypeDef:Timestamp" => Ok(TypeDefKind::Timestamp),
other => Err(TypedefError::Schema(format!(
"unknown TypeDef kind: {other}"
"AlkType:Int8" => Ok(AlkTypeKind::Int8),
"AlkType:Int16" => Ok(AlkTypeKind::Int16),
"AlkType:Int32" => Ok(AlkTypeKind::Int32),
"AlkType:Int64" => Ok(AlkTypeKind::Int64),
"AlkType:Uint8" => Ok(AlkTypeKind::Uint8),
"AlkType:Uint16" => Ok(AlkTypeKind::Uint16),
"AlkType:Uint32" => Ok(AlkTypeKind::Uint32),
"AlkType:Uint64" => Ok(AlkTypeKind::Uint64),
"AlkType:Float32" => Ok(AlkTypeKind::Float32),
"AlkType:Float64" => Ok(AlkTypeKind::Float64),
"AlkType:Boolean" => Ok(AlkTypeKind::Boolean),
"AlkType:Enum" => Ok(AlkTypeKind::Enum),
"AlkType:String" => Ok(AlkTypeKind::String),
"AlkType:Bytes" => Ok(AlkTypeKind::Bytes),
"AlkType:Struct" => Ok(AlkTypeKind::Struct),
"AlkType:Union" => Ok(AlkTypeKind::Union),
"AlkType:Array" => Ok(AlkTypeKind::Array),
"AlkType:Record" => Ok(AlkTypeKind::Record),
"AlkType:Timestamp" => Ok(AlkTypeKind::Timestamp),
other => Err(AlkTypeError::Schema(format!(
"unknown AlkType kind: {other}"
))),
}
}
}
/// Returns the `TypeDef:*` kind string if the schema node declares one.
/// Returns `None` if the node has no `TypeDef:*` keyword.
/// Returns the `AlkType:*` kind string if the schema node declares one.
/// Returns `None` if the node has no `AlkType:*` keyword.
///
/// A TypeDef kind is recognized when the schema object has a key starting
/// with `TypeDef:` whose value is `true`. (Object form with annotations
/// A AlkType kind is recognized when the schema object has a key starting
/// with `AlkType:` whose value is `true`. (Object form with annotations
/// like `{ "encoding": "..." }` is handled by the annotation parsers, not
/// here — `get_typedef_kind` only checks for the presence of the keyword.)
pub fn get_typedef_kind(node: &Value) -> Option<&str> {
/// here — `get_alktype_kind` only checks for the presence of the keyword.)
pub fn get_alktype_kind(node: &Value) -> Option<&str> {
let obj = node.as_object()?;
for key in obj.keys() {
if key.starts_with(TYPEDEF_PREFIX) && obj.get(key) == Some(&Value::Bool(true)) {
if key.starts_with(ALKTYPE_PREFIX) && obj.get(key) == Some(&Value::Bool(true)) {
return Some(key.as_str());
}
}
None
}
/// Returns the `TypeDefKind` enum variant if the schema node declares one
/// (boolean form only, like `get_typedef_kind`).
pub fn get_typedef_kind_enum(node: &Value) -> Option<TypeDefKind> {
get_typedef_kind(node).and_then(|s| s.parse().ok())
/// Returns the `AlkTypeKind` enum variant if the schema node declares one
/// (boolean form only, like `get_alktype_kind`).
pub fn get_alktype_kind_enum(node: &Value) -> Option<AlkTypeKind> {
get_alktype_kind(node).and_then(|s| s.parse().ok())
}
/// Byte endianness for multi-byte integer and float fields.
@@ -316,9 +316,9 @@ pub enum DiscriminatorKind {
Byte {
/// Byte position of the discriminator within the union's buffer.
offset: usize,
/// The `TypeDef:*` kind of the discriminator (typically
/// `TypeDef:Uint8`).
disc_type: TypeDefKind,
/// The `AlkType:*` kind of the discriminator (typically
/// `AlkType:Uint8`).
disc_type: AlkTypeKind,
},
/// Field-name discriminator: a named field within the struct. Mapping keys
/// are string values matching the discriminator field's value. The
@@ -331,36 +331,36 @@ pub enum DiscriminatorKind {
/// Parse the `"discriminator"` annotation from a TUnion schema node.
///
/// Returns [`TypedefError::Schema`] for malformed discriminators (unknown
/// Returns [`AlkTypeError::Schema`] for malformed discriminators (unknown
/// `kind`, missing required `name`, or an unsupported discriminator `type`).
pub fn parse_discriminator(node: &Value) -> Result<DiscriminatorKind, TypedefError> {
pub fn parse_discriminator(node: &Value) -> Result<DiscriminatorKind, AlkTypeError> {
let obj = node.as_object().ok_or_else(|| {
TypedefError::Schema("discriminator requires a schema object".to_string())
AlkTypeError::Schema("discriminator requires a schema object".to_string())
})?;
let disc = obj.get("discriminator").ok_or_else(|| {
TypedefError::Schema("union is missing 'discriminator' annotation".to_string())
AlkTypeError::Schema("union is missing 'discriminator' annotation".to_string())
})?;
let disc_obj = disc
.as_object()
.ok_or_else(|| TypedefError::Schema("'discriminator' must be an object".to_string()))?;
.ok_or_else(|| AlkTypeError::Schema("'discriminator' must be an object".to_string()))?;
let kind = disc_obj
.get("kind")
.and_then(Value::as_str)
.ok_or_else(|| TypedefError::Schema("discriminator is missing 'kind' field".to_string()))?;
.ok_or_else(|| AlkTypeError::Schema("discriminator is missing 'kind' field".to_string()))?;
match kind {
"byte" => {
let offset = disc_obj.get("offset").and_then(Value::as_u64).unwrap_or(0) as usize;
let disc_type_str = disc_obj
.get("type")
.and_then(Value::as_str)
.unwrap_or("TypeDef:Uint8");
let disc_type: TypeDefKind = disc_type_str.parse().map_err(|_| {
TypedefError::Schema(format!(
.unwrap_or("AlkType:Uint8");
let disc_type: AlkTypeKind = disc_type_str.parse().map_err(|_| {
AlkTypeError::Schema(format!(
"discriminator 'type' must be one of {BYTE_DISCRIMINATOR_TYPES:?}, got {disc_type_str:?}"
))
})?;
if !BYTE_DISCRIMINATOR_TYPES.contains(&disc_type) {
return Err(TypedefError::Schema(format!(
return Err(AlkTypeError::Schema(format!(
"discriminator 'type' must be one of {BYTE_DISCRIMINATOR_TYPES:?}, got {disc_type:?}"
)));
}
@@ -374,39 +374,39 @@ pub fn parse_discriminator(node: &Value) -> Result<DiscriminatorKind, TypedefErr
.get("name")
.and_then(Value::as_str)
.ok_or_else(|| {
TypedefError::Schema(
AlkTypeError::Schema(
"field discriminator is missing required 'name' field".to_string(),
)
})?
.to_string();
Ok(DiscriminatorKind::Field { name })
}
other => Err(TypedefError::Schema(format!(
other => Err(AlkTypeError::Schema(format!(
"unknown discriminator 'kind': {other:?} (expected \"byte\" or \"field\")"
))),
}
}
/// Detect a `TypeDef:*` kind from a schema node, accepting either the
/// boolean form (`{ "TypeDef:String": true }`) or the object-annotation
/// form (`{ "TypeDef:String": { "encoding": "..." } }`).
/// Detect a `AlkType:*` kind from a schema node, accepting either the
/// boolean form (`{ "AlkType:String": true }`) or the object-annotation
/// form (`{ "AlkType:String": { "encoding": "..." } }`).
///
/// [`get_typedef_kind`] only recognizes the boolean form; layout computation
/// [`get_alktype_kind`] only recognizes the boolean form; layout computation
/// and engine dispatch also need to recognize the object form so that
/// variable-length encoding annotations don't hide the kind.
pub fn get_typedef_kind_loose(node: &Value) -> Option<&str> {
pub fn get_alktype_kind_loose(node: &Value) -> Option<&str> {
let obj = node.as_object()?;
for key in obj.keys() {
if key.starts_with(TYPEDEF_PREFIX) && obj.get(key).is_some_and(|v| !v.is_null()) {
if key.starts_with(ALKTYPE_PREFIX) && obj.get(key).is_some_and(|v| !v.is_null()) {
return Some(key.as_str());
}
}
None
}
/// Like [`get_typedef_kind_loose`] but returns the parsed [`TypeDefKind`] enum.
pub fn get_typedef_kind_loose_enum(node: &Value) -> Option<TypeDefKind> {
get_typedef_kind_loose(node).and_then(|s| s.parse().ok())
/// Like [`get_alktype_kind_loose`] but returns the parsed [`AlkTypeKind`] enum.
pub fn get_alktype_kind_loose_enum(node: &Value) -> Option<AlkTypeKind> {
get_alktype_kind_loose(node).and_then(|s| s.parse().ok())
}
/// Resolve a `$ref` against the root schema, or return the inline schema.
@@ -473,49 +473,49 @@ mod tests {
use serde_json::json;
#[test]
fn get_typedef_kind_detects_bool_keyword() {
let schema = json!({"TypeDef:Uint32": true});
assert_eq!(get_typedef_kind(&schema), Some("TypeDef:Uint32"));
fn get_alktype_kind_detects_bool_keyword() {
let schema = json!({"AlkType:Uint32": true});
assert_eq!(get_alktype_kind(&schema), Some("AlkType:Uint32"));
}
#[test]
fn get_typedef_kind_ignores_object_keyword() {
let schema = json!({"TypeDef:String": {"encoding": "length-prefixed"}});
assert_eq!(get_typedef_kind(&schema), None);
fn get_alktype_kind_ignores_object_keyword() {
let schema = json!({"AlkType:String": {"encoding": "length-prefixed"}});
assert_eq!(get_alktype_kind(&schema), None);
}
#[test]
fn get_typedef_kind_none_for_plain_schema() {
fn get_alktype_kind_none_for_plain_schema() {
let schema = json!({"type": "object", "properties": {}});
assert_eq!(get_typedef_kind(&schema), None);
assert_eq!(get_alktype_kind(&schema), None);
}
#[test]
fn type_size_fixed_kinds() {
assert_eq!(TypeDefKind::Float32.type_size(), Some(4));
assert_eq!(TypeDefKind::Float64.type_size(), Some(8));
assert_eq!(TypeDefKind::Int8.type_size(), Some(1));
assert_eq!(TypeDefKind::Int16.type_size(), Some(2));
assert_eq!(TypeDefKind::Int32.type_size(), Some(4));
assert_eq!(TypeDefKind::Int64.type_size(), Some(8));
assert_eq!(TypeDefKind::Uint8.type_size(), Some(1));
assert_eq!(TypeDefKind::Uint16.type_size(), Some(2));
assert_eq!(TypeDefKind::Uint32.type_size(), Some(4));
assert_eq!(TypeDefKind::Uint64.type_size(), Some(8));
assert_eq!(TypeDefKind::Boolean.type_size(), Some(1));
assert_eq!(TypeDefKind::Enum.type_size(), Some(4));
assert_eq!(AlkTypeKind::Float32.type_size(), Some(4));
assert_eq!(AlkTypeKind::Float64.type_size(), Some(8));
assert_eq!(AlkTypeKind::Int8.type_size(), Some(1));
assert_eq!(AlkTypeKind::Int16.type_size(), Some(2));
assert_eq!(AlkTypeKind::Int32.type_size(), Some(4));
assert_eq!(AlkTypeKind::Int64.type_size(), Some(8));
assert_eq!(AlkTypeKind::Uint8.type_size(), Some(1));
assert_eq!(AlkTypeKind::Uint16.type_size(), Some(2));
assert_eq!(AlkTypeKind::Uint32.type_size(), Some(4));
assert_eq!(AlkTypeKind::Uint64.type_size(), Some(8));
assert_eq!(AlkTypeKind::Boolean.type_size(), Some(1));
assert_eq!(AlkTypeKind::Enum.type_size(), Some(4));
}
#[test]
fn type_size_variable_and_composite_kinds() {
for kind in [
TypeDefKind::String,
TypeDefKind::Bytes,
TypeDefKind::Struct,
TypeDefKind::Union,
TypeDefKind::Array,
TypeDefKind::Record,
TypeDefKind::Timestamp,
AlkTypeKind::String,
AlkTypeKind::Bytes,
AlkTypeKind::Struct,
AlkTypeKind::Union,
AlkTypeKind::Array,
AlkTypeKind::Record,
AlkTypeKind::Timestamp,
] {
assert_eq!(kind.type_size(), None, "failed for {kind}");
}
@@ -523,60 +523,60 @@ mod tests {
#[test]
fn type_size_unknown_kind_returns_none() {
assert!("TypeDef:Uint128".parse::<TypeDefKind>().is_err());
assert!("TypeDef:Int128".parse::<TypeDefKind>().is_err());
assert!("not-a-typedef".parse::<TypeDefKind>().is_err());
assert!("AlkType:Uint128".parse::<AlkTypeKind>().is_err());
assert!("AlkType:Int128".parse::<AlkTypeKind>().is_err());
assert!("not-an-alktype".parse::<AlkTypeKind>().is_err());
}
#[test]
fn natural_alignment_matches_spec() {
assert_eq!(TypeDefKind::Int8.natural_alignment(), 1);
assert_eq!(TypeDefKind::Uint8.natural_alignment(), 1);
assert_eq!(TypeDefKind::Boolean.natural_alignment(), 1);
assert_eq!(TypeDefKind::Int16.natural_alignment(), 2);
assert_eq!(TypeDefKind::Uint16.natural_alignment(), 2);
assert_eq!(TypeDefKind::Int32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Uint32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Float32.natural_alignment(), 4);
assert_eq!(TypeDefKind::Enum.natural_alignment(), 4);
assert_eq!(TypeDefKind::Float64.natural_alignment(), 8);
assert_eq!(TypeDefKind::Int64.natural_alignment(), 8);
assert_eq!(TypeDefKind::Uint64.natural_alignment(), 8);
assert_eq!(TypeDefKind::String.natural_alignment(), 4);
assert_eq!(TypeDefKind::Bytes.natural_alignment(), 4);
assert_eq!(TypeDefKind::Record.natural_alignment(), 4);
assert_eq!(TypeDefKind::Timestamp.natural_alignment(), 4);
assert_eq!(TypeDefKind::Struct.natural_alignment(), 1);
assert_eq!(TypeDefKind::Union.natural_alignment(), 1);
assert_eq!(TypeDefKind::Array.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Int8.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Uint8.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Boolean.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Int16.natural_alignment(), 2);
assert_eq!(AlkTypeKind::Uint16.natural_alignment(), 2);
assert_eq!(AlkTypeKind::Int32.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Uint32.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Float32.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Enum.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Float64.natural_alignment(), 8);
assert_eq!(AlkTypeKind::Int64.natural_alignment(), 8);
assert_eq!(AlkTypeKind::Uint64.natural_alignment(), 8);
assert_eq!(AlkTypeKind::String.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Bytes.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Record.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Timestamp.natural_alignment(), 4);
assert_eq!(AlkTypeKind::Struct.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Union.natural_alignment(), 1);
assert_eq!(AlkTypeKind::Array.natural_alignment(), 1);
}
#[test]
fn is_fixed_size_classifies_correctly() {
for kind in [
TypeDefKind::Float32,
TypeDefKind::Float64,
TypeDefKind::Int8,
TypeDefKind::Int16,
TypeDefKind::Int32,
TypeDefKind::Int64,
TypeDefKind::Uint8,
TypeDefKind::Uint16,
TypeDefKind::Uint32,
TypeDefKind::Uint64,
TypeDefKind::Boolean,
TypeDefKind::Enum,
AlkTypeKind::Float32,
AlkTypeKind::Float64,
AlkTypeKind::Int8,
AlkTypeKind::Int16,
AlkTypeKind::Int32,
AlkTypeKind::Int64,
AlkTypeKind::Uint8,
AlkTypeKind::Uint16,
AlkTypeKind::Uint32,
AlkTypeKind::Uint64,
AlkTypeKind::Boolean,
AlkTypeKind::Enum,
] {
assert!(kind.is_fixed_size(), "expected fixed: {kind}");
}
for kind in [
TypeDefKind::String,
TypeDefKind::Bytes,
TypeDefKind::Struct,
TypeDefKind::Union,
TypeDefKind::Array,
TypeDefKind::Record,
TypeDefKind::Timestamp,
AlkTypeKind::String,
AlkTypeKind::Bytes,
AlkTypeKind::Struct,
AlkTypeKind::Union,
AlkTypeKind::Array,
AlkTypeKind::Record,
AlkTypeKind::Timestamp,
] {
assert!(!kind.is_fixed_size(), "expected variable: {kind}");
}
@@ -674,7 +674,7 @@ mod tests {
disc,
DiscriminatorKind::Byte {
offset: 0,
disc_type: TypeDefKind::Uint8,
disc_type: AlkTypeKind::Uint8,
}
);
}
@@ -682,14 +682,14 @@ mod tests {
#[test]
fn parse_discriminator_byte_explicit() {
let schema = json!({
"discriminator": {"kind": "byte", "offset": 4, "type": "TypeDef:Uint16"}
"discriminator": {"kind": "byte", "offset": 4, "type": "AlkType:Uint16"}
});
let disc = parse_discriminator(&schema).expect("byte discriminator");
assert_eq!(
disc,
DiscriminatorKind::Byte {
offset: 4,
disc_type: TypeDefKind::Uint16,
disc_type: AlkTypeKind::Uint16,
}
);
}
@@ -708,10 +708,10 @@ mod tests {
#[test]
fn parse_discriminator_missing_discriminator_is_error() {
let schema = json!({"TypeDef:Union": true});
let schema = json!({"AlkType:Union": true});
assert!(matches!(
parse_discriminator(&schema),
Err(TypedefError::Schema(_))
Err(AlkTypeError::Schema(_))
));
}
@@ -720,7 +720,7 @@ mod tests {
let schema = json!({"discriminator": {"kind": "field"}});
assert!(matches!(
parse_discriminator(&schema),
Err(TypedefError::Schema(_))
Err(AlkTypeError::Schema(_))
));
}
@@ -729,18 +729,18 @@ mod tests {
let schema = json!({"discriminator": {"kind": "magic"}});
assert!(matches!(
parse_discriminator(&schema),
Err(TypedefError::Schema(_))
Err(AlkTypeError::Schema(_))
));
}
#[test]
fn parse_discriminator_byte_invalid_type_is_error() {
let schema = json!({
"discriminator": {"kind": "byte", "type": "TypeDef:Float32"}
"discriminator": {"kind": "byte", "type": "AlkType:Float32"}
});
assert!(matches!(
parse_discriminator(&schema),
Err(TypedefError::Schema(_))
Err(AlkTypeError::Schema(_))
));
}
@@ -798,14 +798,14 @@ mod tests {
fn normalize_refs_preserves_sibling_keys() {
let mut schema = json!({
"$ref": "Read",
"typedef:annotation": "kept"
"alktype:annotation": "kept"
});
normalize_refs(&mut schema);
assert_eq!(
schema,
json!({
"$ref": "#/$defs/Read",
"typedef:annotation": "kept"
"alktype:annotation": "kept"
})
);
}
+204 -204
View File
File diff suppressed because it is too large. Load diff
+106 -106
View File
@@ -10,8 +10,8 @@
//! endianness handling are uniform with the rest of the engine.
use crate::data_access::{read_enum, read_string, read_u16, read_u32, read_u8};
use crate::error::TypedefError;
use crate::schema::{get_typedef_kind, parse_discriminator, DiscriminatorKind, Endian, TypeDefKind, DISCRIMINATOR_PATH, U32_SIZE};
use crate::error::AlkTypeError;
use crate::schema::{get_alktype_kind, parse_discriminator, DiscriminatorKind, Endian, AlkTypeKind, DISCRIMINATOR_PATH, U32_SIZE};
use serde_json::Value;
const STRING_PREFIX_SIZE: usize = 4;
@@ -39,36 +39,36 @@ pub struct UnionDispatch {
///
/// # Errors
///
/// - [`TypedefError::Schema`] if the discriminator annotation is missing
/// - [`AlkTypeError::Schema`] if the discriminator annotation is missing
/// or malformed, or if the discriminator `type` is not one of
/// `TypeDef:Uint8` / `TypeDef:Uint16` / `TypeDef:Uint32`.
/// - [`TypedefError::Access`] if the buffer is too short to contain the
/// `AlkType:Uint8` / `AlkType:Uint16` / `AlkType:Uint32`.
/// - [`AlkTypeError::Access`] if the buffer is too short to contain the
/// discriminator, or if the read value is not present in the union's
/// `mapping`.
pub fn read_byte_discriminator(
buffer: &[u8],
union_schema: &Value,
endian: Endian,
) -> Result<UnionDispatch, TypedefError> {
) -> Result<UnionDispatch, AlkTypeError> {
let disc = parse_discriminator(union_schema)?;
let (offset, disc_type) = match disc {
DiscriminatorKind::Byte { offset, disc_type } => (offset, disc_type),
DiscriminatorKind::Field { .. } => {
return Err(TypedefError::Schema(
return Err(AlkTypeError::Schema(
"read_byte_discriminator requires a byte-offset discriminator".to_string(),
));
}
};
let (disc_value, discriminator_size) = match disc_type {
TypeDefKind::Uint8 => (u32::from(read_u8(buffer, offset, DISCRIMINATOR_PATH)?), 1),
TypeDefKind::Uint16 => (
AlkTypeKind::Uint8 => (u32::from(read_u8(buffer, offset, DISCRIMINATOR_PATH)?), 1),
AlkTypeKind::Uint16 => (
u32::from(read_u16(buffer, offset, DISCRIMINATOR_PATH, endian)?),
2,
),
TypeDefKind::Uint32 => (read_u32(buffer, offset, DISCRIMINATOR_PATH, endian)?, 4),
AlkTypeKind::Uint32 => (read_u32(buffer, offset, DISCRIMINATOR_PATH, endian)?, 4),
other => {
return Err(TypedefError::Schema(format!(
return Err(AlkTypeError::Schema(format!(
"unsupported byte discriminator type: {other}"
)));
}
@@ -80,7 +80,7 @@ pub fn read_byte_discriminator(
let variant_offset =
offset
.checked_add(discriminator_size)
.ok_or_else(|| TypedefError::Access {
.ok_or_else(|| AlkTypeError::Access {
field_path: DISCRIMINATOR_PATH.to_string(),
reason: format!(
"offset {offset} + discriminator_size {discriminator_size} overflows usize"
@@ -105,12 +105,12 @@ pub fn read_byte_discriminator(
///
/// # Errors
///
/// - [`TypedefError::Schema`] if the discriminator annotation is missing
/// - [`AlkTypeError::Schema`] if the discriminator annotation is missing
/// or malformed, the discriminator field is not declared in
/// `properties`, the field has no `TypeDef:*` kind, or the field's
/// kind is not one of `TypeDef:String` / `TypeDef:Uint8` /
/// `TypeDef:Enum`.
/// - [`TypedefError::Access`] if the buffer is too short to contain the
/// `properties`, the field has no `AlkType:*` kind, or the field's
/// kind is not one of `AlkType:String` / `AlkType:Uint8` /
/// `AlkType:Enum`.
/// - [`AlkTypeError::Access`] if the buffer is too short to contain the
/// discriminator field, or if the read value is not present in the
/// union's `mapping`.
pub fn read_field_discriminator(
@@ -118,12 +118,12 @@ pub fn read_field_discriminator(
union_schema: &Value,
disc_field_offset: usize,
endian: Endian,
) -> Result<UnionDispatch, TypedefError> {
) -> Result<UnionDispatch, AlkTypeError> {
let disc = parse_discriminator(union_schema)?;
let name = match disc {
DiscriminatorKind::Field { name } => name,
DiscriminatorKind::Byte { .. } => {
return Err(TypedefError::Schema(
return Err(AlkTypeError::Schema(
"read_field_discriminator requires a field-name discriminator".to_string(),
));
}
@@ -134,26 +134,26 @@ pub fn read_field_discriminator(
.and_then(Value::as_object)
.and_then(|props| props.get(&name))
.ok_or_else(|| {
TypedefError::Schema(format!(
AlkTypeError::Schema(format!(
"discriminator field '{name}' not found in union properties"
))
})?;
let kind = get_typedef_kind(field_schema)
.and_then(|s| s.parse::<TypeDefKind>().ok())
let kind = get_alktype_kind(field_schema)
.and_then(|s| s.parse::<AlkTypeKind>().ok())
.ok_or_else(|| {
TypedefError::Schema(format!(
"discriminator field '{name}' has no TypeDef:* kind"
AlkTypeError::Schema(format!(
"discriminator field '{name}' has no AlkType:* kind"
))
})?;
let (key, discriminator_field_size) = match kind {
TypeDefKind::String => {
AlkTypeKind::String => {
let s = read_string(buffer, disc_field_offset, &name, endian)?;
let size =
STRING_PREFIX_SIZE
.checked_add(s.len())
.ok_or_else(|| TypedefError::Access {
.ok_or_else(|| AlkTypeError::Access {
field_path: name.clone(),
reason: format!(
"string prefix {STRING_PREFIX_SIZE} + data length {} overflows usize",
@@ -162,16 +162,16 @@ pub fn read_field_discriminator(
})?;
(s.to_string(), size)
}
TypeDefKind::Uint8 => {
AlkTypeKind::Uint8 => {
let v = read_u8(buffer, disc_field_offset, &name)?;
(v.to_string(), 1)
}
TypeDefKind::Enum => {
AlkTypeKind::Enum => {
let v = read_enum(buffer, disc_field_offset, &name, endian)?;
(v.to_string(), U32_SIZE)
}
other => {
return Err(TypedefError::Schema(format!(
return Err(AlkTypeError::Schema(format!(
"unsupported discriminator field type: {other}"
)));
}
@@ -181,7 +181,7 @@ pub fn read_field_discriminator(
let variant_offset = disc_field_offset
.checked_add(discriminator_field_size)
.ok_or_else(|| TypedefError::Access {
.ok_or_else(|| AlkTypeError::Access {
field_path: name.clone(),
reason: format!(
"disc_field_offset {disc_field_offset} + discriminator_field_size {discriminator_field_size} overflows usize"
@@ -201,24 +201,24 @@ pub fn read_field_discriminator(
/// `$ref` pointers of the form `"#/$defs/<name>"` are resolved against
/// the `union_schema`'s own `$defs` block (when the union schema is the
/// schema root). For nested unions whose `$defs` live on an ancestor,
/// the caller (typically `TypedefEngine::compile`) is expected to
/// the caller (typically `AlkTypeEngine::compile`) is expected to
/// resolve refs before reaching this function, or to inline the
/// variant schemas into the mapping at load time.
///
/// # Errors
///
/// - [`TypedefError::Schema`] if the union has no `mapping` object, the
/// - [`AlkTypeError::Schema`] if the union has no `mapping` object, the
/// `key` is not present, a `$ref` is malformed, or a `$ref` cannot be
/// resolved against the union schema's own `$defs`.
pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Value, TypedefError> {
pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Value, AlkTypeError> {
let mapping = union_schema
.get("mapping")
.and_then(Value::as_object)
.ok_or_else(|| TypedefError::Schema("union is missing 'mapping' object".to_string()))?;
.ok_or_else(|| AlkTypeError::Schema("union is missing 'mapping' object".to_string()))?;
let variant = mapping
.get(key)
.ok_or_else(|| TypedefError::Schema(format!("unknown mapping key: {key}")))?;
.ok_or_else(|| AlkTypeError::Schema(format!("unknown mapping key: {key}")))?;
let ref_str = match variant.get("$ref").and_then(Value::as_str) {
Some(r) => r,
@@ -227,10 +227,10 @@ pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Val
let pointer = ref_str
.strip_prefix('#')
.ok_or_else(|| TypedefError::Schema(format!("unsupported $ref form: {ref_str}")))?;
.ok_or_else(|| AlkTypeError::Schema(format!("unsupported $ref form: {ref_str}")))?;
let resolved = resolve_json_pointer(union_schema, pointer).ok_or_else(|| {
TypedefError::Schema(format!(
AlkTypeError::Schema(format!(
"cannot resolve $ref {ref_str} against union schema; ensure refs are inlined or the union schema contains $defs"
))
})?;
@@ -239,27 +239,27 @@ pub fn resolve_variant<'a>(union_schema: &'a Value, key: &str) -> Result<&'a Val
/// Get the discriminator size in bytes for a byte-offset discriminator.
///
/// Returns 1 for `TypeDef:Uint8`, 2 for `TypeDef:Uint16`, and 4 for
/// `TypeDef:Uint32`. Field-name discriminators have no fixed size and
/// produce a [`TypedefError::Schema`].
/// Returns 1 for `AlkType:Uint8`, 2 for `AlkType:Uint16`, and 4 for
/// `AlkType:Uint32`. Field-name discriminators have no fixed size and
/// produce a [`AlkTypeError::Schema`].
///
/// # Errors
///
/// - [`TypedefError::Schema`] if the discriminator annotation is
/// - [`AlkTypeError::Schema`] if the discriminator annotation is
/// missing/malformed, the discriminator `type` is unsupported, or the
/// discriminator is a field-name discriminator.
pub fn discriminator_size(union_schema: &Value) -> Result<usize, TypedefError> {
pub fn discriminator_size(union_schema: &Value) -> Result<usize, AlkTypeError> {
let disc = parse_discriminator(union_schema)?;
match disc {
DiscriminatorKind::Byte { disc_type, .. } => match disc_type {
TypeDefKind::Uint8 => Ok(1),
TypeDefKind::Uint16 => Ok(2),
TypeDefKind::Uint32 => Ok(4),
other => Err(TypedefError::Schema(format!(
AlkTypeKind::Uint8 => Ok(1),
AlkTypeKind::Uint16 => Ok(2),
AlkTypeKind::Uint32 => Ok(4),
other => Err(AlkTypeError::Schema(format!(
"unsupported byte discriminator type: {other}"
))),
},
DiscriminatorKind::Field { .. } => Err(TypedefError::Schema(
DiscriminatorKind::Field { .. } => Err(AlkTypeError::Schema(
"field-name discriminator has no fixed size".to_string(),
)),
}
@@ -270,7 +270,7 @@ fn verify_mapping_key(
key: &str,
field_path: &str,
raw_value: &str,
) -> Result<(), TypedefError> {
) -> Result<(), AlkTypeError> {
let in_mapping = union_schema
.get("mapping")
.and_then(Value::as_object)
@@ -279,7 +279,7 @@ fn verify_mapping_key(
if in_mapping {
Ok(())
} else {
Err(TypedefError::Access {
Err(AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("unknown discriminator value: {raw_value}"),
})
@@ -325,43 +325,43 @@ mod tests {
fn byte_union_schema(offset: usize, disc_type: &str) -> Value {
json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "byte", "offset": offset, "type": disc_type},
"mapping": {
"5": {"TypeDef:Struct": true, "properties": {"id": {"TypeDef:Uint32": true}}},
"6": {"TypeDef:Struct": true, "properties": {"len": {"TypeDef:Uint16": true}}}
"5": {"AlkType:Struct": true, "properties": {"id": {"AlkType:Uint32": true}}},
"6": {"AlkType:Struct": true, "properties": {"len": {"AlkType:Uint16": true}}}
}
})
}
fn field_union_schema(field_name: &str, field_kind: &str) -> Value {
let field_schema = match field_kind {
"TypeDef:Enum" => json!({
"TypeDef:Enum": true,
"AlkType:Enum" => json!({
"AlkType:Enum": true,
"enum": ["read", "write"]
}),
_ => json!({field_kind: true}),
};
let (key_a, key_b) = match field_kind {
"TypeDef:String" => ("read", "write"),
"AlkType:String" => ("read", "write"),
_ => ("0", "1"),
};
json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "field", "name": field_name},
"properties": {
field_name: field_schema
},
"mapping": {
key_a: {"TypeDef:Struct": true, "properties": {"n": {"TypeDef:Uint32": true}}},
key_b: {"TypeDef:Struct": true, "properties": {"m": {"TypeDef:Uint16": true}}}
key_a: {"AlkType:Struct": true, "properties": {"n": {"AlkType:Uint32": true}}},
key_b: {"AlkType:Struct": true, "properties": {"m": {"AlkType:Uint16": true}}}
}
})
}
#[test]
fn read_byte_discriminator_uint8_default_offset() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let buf = [5u8, 0xAA, 0xBB, 0xCC];
let d = read_byte_discriminator(&buf, &schema, LE).expect("read");
assert_eq!(d.key, "5");
@@ -371,7 +371,7 @@ mod tests {
#[test]
fn read_byte_discriminator_uint8_big_endian() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let buf = [6u8];
let d = read_byte_discriminator(&buf, &schema, BE).expect("read");
assert_eq!(d.key, "6");
@@ -380,7 +380,7 @@ mod tests {
#[test]
fn read_byte_discriminator_uint16_little_endian() {
let schema = byte_union_schema(2, "TypeDef:Uint16");
let schema = byte_union_schema(2, "AlkType:Uint16");
let mut buf = vec![0u8; 4];
buf[2..4].copy_from_slice(&5u16.to_le_bytes());
let d = read_byte_discriminator(&buf, &schema, LE).expect("read");
@@ -391,7 +391,7 @@ mod tests {
#[test]
fn read_byte_discriminator_uint16_big_endian() {
let schema = byte_union_schema(0, "TypeDef:Uint16");
let schema = byte_union_schema(0, "AlkType:Uint16");
let buf = [0x00, 0x06, 0xAA, 0xBB];
let d = read_byte_discriminator(&buf, &schema, BE).expect("read");
assert_eq!(d.key, "6");
@@ -400,7 +400,7 @@ mod tests {
#[test]
fn read_byte_discriminator_uint32_little_endian() {
let schema = byte_union_schema(0, "TypeDef:Uint32");
let schema = byte_union_schema(0, "AlkType:Uint32");
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&5u32.to_le_bytes());
let d = read_byte_discriminator(&buf, &schema, LE).expect("read");
@@ -411,7 +411,7 @@ mod tests {
#[test]
fn read_byte_discriminator_uint32_big_endian() {
let schema = byte_union_schema(0, "TypeDef:Uint32");
let schema = byte_union_schema(0, "AlkType:Uint32");
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&6u32.to_be_bytes());
let d = read_byte_discriminator(&buf, &schema, BE).expect("read");
@@ -421,11 +421,11 @@ mod tests {
#[test]
fn read_byte_discriminator_unknown_value_is_access_error() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let buf = [99u8];
let err = read_byte_discriminator(&buf, &schema, LE).unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, DISCRIMINATOR_PATH);
assert!(reason.contains("99"), "reason: {reason}");
}
@@ -435,23 +435,23 @@ mod tests {
#[test]
fn read_byte_discriminator_buffer_too_short_is_access_error() {
let schema = byte_union_schema(4, "TypeDef:Uint32");
let schema = byte_union_schema(4, "AlkType:Uint32");
let buf = [0u8; 2];
let err = read_byte_discriminator(&buf, &schema, LE).unwrap_err();
assert!(matches!(err, TypedefError::Access { .. }));
assert!(matches!(err, AlkTypeError::Access { .. }));
}
#[test]
fn read_byte_discriminator_field_kind_is_schema_error() {
let schema = field_union_schema("type", "TypeDef:String");
let schema = field_union_schema("type", "AlkType:String");
let buf = [0u8; 16];
let err = read_byte_discriminator(&buf, &schema, LE).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn read_field_discriminator_string() {
let schema = field_union_schema("type", "TypeDef:String");
let schema = field_union_schema("type", "AlkType:String");
let mut buf = vec![0u8; 32];
let value = "read";
let len_bytes = (value.len() as u32).to_le_bytes();
@@ -465,7 +465,7 @@ mod tests {
#[test]
fn read_field_discriminator_uint8() {
let schema = field_union_schema("type", "TypeDef:Uint8");
let schema = field_union_schema("type", "AlkType:Uint8");
let mut buf = vec![0u8; 8];
buf[0] = 0;
let d = read_field_discriminator(&buf, &schema, 0, LE).expect("read");
@@ -476,7 +476,7 @@ mod tests {
#[test]
fn read_field_discriminator_enum() {
let schema = field_union_schema("type", "TypeDef:Enum");
let schema = field_union_schema("type", "AlkType:Enum");
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&0u32.to_le_bytes());
let d = read_field_discriminator(&buf, &schema, 0, LE).expect("read");
@@ -487,7 +487,7 @@ mod tests {
#[test]
fn read_field_discriminator_string_big_endian() {
let schema = field_union_schema("type", "TypeDef:String");
let schema = field_union_schema("type", "AlkType:String");
let mut buf = vec![0u8; 32];
let value = "write";
let len_bytes = (value.len() as u32).to_be_bytes();
@@ -500,12 +500,12 @@ mod tests {
#[test]
fn read_field_discriminator_unknown_value_is_access_error() {
let schema = field_union_schema("type", "TypeDef:Uint8");
let schema = field_union_schema("type", "AlkType:Uint8");
let mut buf = vec![0u8; 8];
buf[0] = 99;
let err = read_field_discriminator(&buf, &schema, 0, LE).unwrap_err();
match err {
TypedefError::Access { field_path, reason } => {
AlkTypeError::Access { field_path, reason } => {
assert_eq!(field_path, "type");
assert!(reason.contains("99"), "reason: {reason}");
}
@@ -516,51 +516,51 @@ mod tests {
#[test]
fn read_field_discriminator_field_not_found_is_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "field", "name": "missing"},
"properties": {"other": {"TypeDef:Uint8": true}},
"mapping": {"5": {"TypeDef:Struct": true}}
"properties": {"other": {"AlkType:Uint8": true}},
"mapping": {"5": {"AlkType:Struct": true}}
});
let buf = [0u8; 4];
let err = read_field_discriminator(&buf, &schema, 0, LE).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn read_field_discriminator_no_typedef_kind_is_schema_error() {
fn read_field_discriminator_no_alktype_kind_is_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "field", "name": "type"},
"properties": {"type": {"type": "string"}},
"mapping": {"read": {"TypeDef:Struct": true}}
"mapping": {"read": {"AlkType:Struct": true}}
});
let buf = [0u8; 4];
let err = read_field_discriminator(&buf, &schema, 0, LE).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn read_field_discriminator_unsupported_kind_is_schema_error() {
let schema = field_union_schema("type", "TypeDef:Float32");
let schema = field_union_schema("type", "AlkType:Float32");
let buf = [0u8; 8];
let err = read_field_discriminator(&buf, &schema, 0, LE).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn read_field_discriminator_byte_kind_is_schema_error() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let buf = [5u8];
let err = read_field_discriminator(&buf, &schema, 0, LE).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn resolve_variant_inline_schema() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let variant = resolve_variant(&schema, "5").expect("resolve");
assert_eq!(
variant.get("TypeDef:Struct").and_then(Value::as_bool),
variant.get("AlkType:Struct").and_then(Value::as_bool),
Some(true)
);
}
@@ -568,78 +568,78 @@ mod tests {
#[test]
fn resolve_variant_ref_against_own_defs() {
let schema = json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "byte"},
"mapping": {
"5": {"$ref": "#/$defs/Read"}
},
"$defs": {
"Read": {"TypeDef:Struct": true, "properties": {"id": {"TypeDef:Uint32": true}}}
"Read": {"AlkType:Struct": true, "properties": {"id": {"AlkType:Uint32": true}}}
}
});
let variant = resolve_variant(&schema, "5").expect("resolve");
assert_eq!(
variant.get("TypeDef:Struct").and_then(Value::as_bool),
variant.get("AlkType:Struct").and_then(Value::as_bool),
Some(true)
);
}
#[test]
fn resolve_variant_unknown_key_is_schema_error() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
let err = resolve_variant(&schema, "999").unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn resolve_variant_missing_mapping_is_schema_error() {
let schema = json!({"TypeDef:Union": true, "discriminator": {"kind": "byte"}});
let schema = json!({"AlkType:Union": true, "discriminator": {"kind": "byte"}});
let err = resolve_variant(&schema, "5").unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn resolve_variant_unresolvable_ref_is_schema_error() {
let schema = json!({
"TypeDef:Union": true,
"AlkType:Union": true,
"discriminator": {"kind": "byte"},
"mapping": {
"5": {"$ref": "#/$defs/Read"}
}
});
let err = resolve_variant(&schema, "5").unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn discriminator_size_uint8() {
let schema = byte_union_schema(0, "TypeDef:Uint8");
let schema = byte_union_schema(0, "AlkType:Uint8");
assert_eq!(discriminator_size(&schema).unwrap(), 1);
}
#[test]
fn discriminator_size_uint16() {
let schema = byte_union_schema(0, "TypeDef:Uint16");
let schema = byte_union_schema(0, "AlkType:Uint16");
assert_eq!(discriminator_size(&schema).unwrap(), 2);
}
#[test]
fn discriminator_size_uint32() {
let schema = byte_union_schema(0, "TypeDef:Uint32");
let schema = byte_union_schema(0, "AlkType:Uint32");
assert_eq!(discriminator_size(&schema).unwrap(), 4);
}
#[test]
fn discriminator_size_field_kind_is_schema_error() {
let schema = field_union_schema("type", "TypeDef:String");
let schema = field_union_schema("type", "AlkType:String");
let err = discriminator_size(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
#[test]
fn discriminator_size_missing_discriminator_is_schema_error() {
let schema = json!({"TypeDef:Union": true});
let schema = json!({"AlkType:Union": true});
let err = discriminator_size(&schema).unwrap_err();
assert!(matches!(err, TypedefError::Schema(_)));
assert!(matches!(err, AlkTypeError::Schema(_)));
}
}
+89 -89
View File
@@ -1,4 +1,4 @@
//! Custom keyword validators for all 17 `TypeDef:*` kinds, registered
//! Custom keyword validators for all 17 `AlkType:*` kinds, registered
//! via `jsonschema::options().with_keyword(...)`.
//!
//! Per ADR-098: the `jsonschema` crate handles all structural validation;
@@ -10,11 +10,11 @@
//! factories read parent context (e.g. `maxLength`) to pass into the
//! validator struct.
use crate::error::TypedefError;
use crate::error::AlkTypeError;
use jsonschema::{Keyword, ValidationError};
use serde_json::{Map, Value};
/// Build a jsonschema validator with all 17 `TypeDef:*` custom keywords
/// Build a jsonschema validator with all 17 `AlkType:*` custom keywords
/// registered.
///
/// The returned validator can validate JSON representations of data
@@ -25,43 +25,43 @@ use serde_json::{Map, Value};
///
/// # Errors
///
/// Returns [`TypedefError::Schema`] if the schema is malformed or the
/// Returns [`AlkTypeError::Schema`] if the schema is malformed or the
/// underlying jsonschema validator cannot be built.
pub fn build_validator(schema: &Value) -> Result<jsonschema::Validator, TypedefError> {
pub fn build_validator(schema: &Value) -> Result<jsonschema::Validator, AlkTypeError> {
jsonschema::options()
.with_keyword("TypeDef:Float32", float32_factory)
.with_keyword("TypeDef:Float64", float64_factory)
.with_keyword("TypeDef:Int8", int8_factory)
.with_keyword("TypeDef:Int16", int16_factory)
.with_keyword("TypeDef:Int32", int32_factory)
.with_keyword("TypeDef:Int64", int64_factory)
.with_keyword("TypeDef:Uint8", uint8_factory)
.with_keyword("TypeDef:Uint16", uint16_factory)
.with_keyword("TypeDef:Uint32", uint32_factory)
.with_keyword("TypeDef:Uint64", uint64_factory)
.with_keyword("TypeDef:Boolean", boolean_factory)
.with_keyword("TypeDef:String", string_factory)
.with_keyword("TypeDef:Bytes", bytes_factory)
.with_keyword("TypeDef:Enum", enum_factory)
.with_keyword("TypeDef:Struct", struct_factory)
.with_keyword("TypeDef:Union", union_factory)
.with_keyword("TypeDef:Array", array_factory)
.with_keyword("TypeDef:Record", record_factory)
.with_keyword("TypeDef:Timestamp", timestamp_factory)
.with_keyword("AlkType:Float32", float32_factory)
.with_keyword("AlkType:Float64", float64_factory)
.with_keyword("AlkType:Int8", int8_factory)
.with_keyword("AlkType:Int16", int16_factory)
.with_keyword("AlkType:Int32", int32_factory)
.with_keyword("AlkType:Int64", int64_factory)
.with_keyword("AlkType:Uint8", uint8_factory)
.with_keyword("AlkType:Uint16", uint16_factory)
.with_keyword("AlkType:Uint32", uint32_factory)
.with_keyword("AlkType:Uint64", uint64_factory)
.with_keyword("AlkType:Boolean", boolean_factory)
.with_keyword("AlkType:String", string_factory)
.with_keyword("AlkType:Bytes", bytes_factory)
.with_keyword("AlkType:Enum", enum_factory)
.with_keyword("AlkType:Struct", struct_factory)
.with_keyword("AlkType:Union", union_factory)
.with_keyword("AlkType:Array", array_factory)
.with_keyword("AlkType:Record", record_factory)
.with_keyword("AlkType:Timestamp", timestamp_factory)
.build(schema)
.map_err(|e| TypedefError::Schema(format!("validator build failed: {e}")))
.map_err(|e| AlkTypeError::Schema(format!("validator build failed: {e}")))
}
// ---------------------------------------------------------------------------
// Numeric validators (generated via macros)
// ---------------------------------------------------------------------------
define_int_validator!(Int8Validator, int8_factory, "TypeDef:Int8", -128, 127);
define_int_validator!(Int16Validator, int16_factory, "TypeDef:Int16", -32768, 32767);
define_int_validator!(Int32Validator, int32_factory, "TypeDef:Int32", -2147483648, 2147483647);
define_uint_validator!(Uint8Validator, uint8_factory, "TypeDef:Uint8", 255);
define_uint_validator!(Uint16Validator, uint16_factory, "TypeDef:Uint16", 65535);
define_uint_validator!(Uint32Validator, uint32_factory, "TypeDef:Uint32", 4294967295);
define_int_validator!(Int8Validator, int8_factory, "AlkType:Int8", -128, 127);
define_int_validator!(Int16Validator, int16_factory, "AlkType:Int16", -32768, 32767);
define_int_validator!(Int32Validator, int32_factory, "AlkType:Int32", -2147483648, 2147483647);
define_uint_validator!(Uint8Validator, uint8_factory, "AlkType:Uint8", 255);
define_uint_validator!(Uint16Validator, uint16_factory, "AlkType:Uint16", 65535);
define_uint_validator!(Uint32Validator, uint32_factory, "AlkType:Uint32", 4294967295);
// Int64/Uint64 use the full i64/u64 range, so the macro's `n <= $max` check
// is always true (clippy: "comparison useless due to type limits"). Write
@@ -88,7 +88,7 @@ fn int64_factory<'a>(
if value.as_bool() == Some(true) {
Ok(Box::new(Int64Validator))
} else {
Err(ValidationError::schema("TypeDef:Int64 must be set to true"))
Err(ValidationError::schema("AlkType:Int64 must be set to true"))
}
}
@@ -113,19 +113,19 @@ fn uint64_factory<'a>(
if value.as_bool() == Some(true) {
Ok(Box::new(Uint64Validator))
} else {
Err(ValidationError::schema("TypeDef:Uint64 must be set to true"))
Err(ValidationError::schema("AlkType:Uint64 must be set to true"))
}
}
define_float_validator!(
Float32Validator,
float32_factory,
"TypeDef:Float32",
"AlkType:Float32",
"expected a finite f32-compatible number"
);
define_float_validator!(
Float64Validator,
float64_factory,
"TypeDef:Float64",
"AlkType:Float64",
"expected a finite f64 number"
);
@@ -187,7 +187,7 @@ impl Keyword for BytesValidator {
}
}
/// `TypeDef:Enum` is a layout marker — the built-in `enum` keyword
/// `AlkType:Enum` is a layout marker — the built-in `enum` keyword
/// handles value-membership validation. The custom keyword exists solely
/// for the layout engine to recognize the type as a fixed-size u32 index.
struct EnumValidator;
@@ -257,11 +257,11 @@ fn is_rfc3339_timestamp(s: &str) -> bool {
// Composite validators (generated via macros)
// ---------------------------------------------------------------------------
define_type_validator!(StructValidator, struct_factory, "TypeDef:Struct", is_object, "expected an object");
define_type_validator!(UnionValidator, union_factory, "TypeDef:Union", is_object, "expected an object for union");
define_type_validator!(ArrayValidator, array_factory, "TypeDef:Array", is_array, "expected an array");
define_type_validator!(RecordValidator, record_factory, "TypeDef:Record", is_object, "expected an object for record");
define_type_validator!(BooleanValidator, boolean_factory, "TypeDef:Boolean", is_boolean, "expected a boolean");
define_type_validator!(StructValidator, struct_factory, "AlkType:Struct", is_object, "expected an object");
define_type_validator!(UnionValidator, union_factory, "AlkType:Union", is_object, "expected an object for union");
define_type_validator!(ArrayValidator, array_factory, "AlkType:Array", is_array, "expected an array");
define_type_validator!(RecordValidator, record_factory, "AlkType:Record", is_object, "expected an object for record");
define_type_validator!(BooleanValidator, boolean_factory, "AlkType:Boolean", is_boolean, "expected a boolean");
// ---------------------------------------------------------------------------
// Factory closures for non-macro-generated validators
@@ -274,7 +274,7 @@ fn string_factory<'a>(
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
if !value.is_boolean() && !value.is_object() {
return Err(ValidationError::schema(
"TypeDef:String must be set to true or an annotation object",
"AlkType:String must be set to true or an annotation object",
));
}
let max_length = parent
@@ -291,7 +291,7 @@ fn bytes_factory<'a>(
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
if !value.is_boolean() && !value.is_object() {
return Err(ValidationError::schema(
"TypeDef:Bytes must be set to true or an annotation object",
"AlkType:Bytes must be set to true or an annotation object",
));
}
let max_length = parent
@@ -309,7 +309,7 @@ fn enum_factory<'a>(
if value.as_bool() == Some(true) {
Ok(Box::new(EnumValidator))
} else {
Err(ValidationError::schema("TypeDef:Enum must be set to true"))
Err(ValidationError::schema("AlkType:Enum must be set to true"))
}
}
@@ -322,7 +322,7 @@ fn timestamp_factory<'a>(
Ok(Box::new(TimestampValidator))
} else {
Err(ValidationError::schema(
"TypeDef:Timestamp must be set to true",
"AlkType:Timestamp must be set to true",
))
}
}
@@ -339,13 +339,13 @@ mod tests {
#[test]
fn validates_valid_struct_instance() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"id": { "TypeDef:Uint32": true, "type": "integer" },
"score": { "TypeDef:Float32": true, "type": "number" },
"flag": { "TypeDef:Uint8": true, "type": "integer" },
"count": { "TypeDef:Uint16": true, "type": "integer" }
"id": { "AlkType:Uint32": true, "type": "integer" },
"score": { "AlkType:Float32": true, "type": "number" },
"flag": { "AlkType:Uint8": true, "type": "integer" },
"count": { "AlkType:Uint16": true, "type": "integer" }
},
"required": ["id", "score", "flag", "count"]
});
@@ -362,9 +362,9 @@ mod tests {
#[test]
fn rejects_uint32_out_of_range() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": { "id": { "TypeDef:Uint32": true, "type": "integer" } },
"properties": { "id": { "AlkType:Uint32": true, "type": "integer" } },
"required": ["id"]
});
let validator = validator_for(&schema);
@@ -375,9 +375,9 @@ mod tests {
#[test]
fn validates_int8_range() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": { "val": { "TypeDef:Int8": true, "type": "integer" } },
"properties": { "val": { "AlkType:Int8": true, "type": "integer" } },
"required": ["val"]
});
let validator = validator_for(&schema);
@@ -391,11 +391,11 @@ mod tests {
#[test]
fn validates_int16_and_int32_ranges() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"i16": { "TypeDef:Int16": true, "type": "integer" },
"i32": { "TypeDef:Int32": true, "type": "integer" }
"i16": { "AlkType:Int16": true, "type": "integer" },
"i32": { "AlkType:Int32": true, "type": "integer" }
},
"required": ["i16", "i32"]
});
@@ -409,11 +409,11 @@ mod tests {
#[test]
fn validates_uint16_and_uint32_ranges() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"u16": { "TypeDef:Uint16": true, "type": "integer" },
"u32": { "TypeDef:Uint32": true, "type": "integer" }
"u16": { "AlkType:Uint16": true, "type": "integer" },
"u32": { "AlkType:Uint32": true, "type": "integer" }
},
"required": ["u16", "u32"]
});
@@ -426,9 +426,9 @@ mod tests {
#[test]
fn validates_int64_range() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": { "val": { "TypeDef:Int64": true, "type": "integer" } },
"properties": { "val": { "AlkType:Int64": true, "type": "integer" } },
"required": ["val"]
});
let validator = validator_for(&schema);
@@ -441,9 +441,9 @@ mod tests {
#[test]
fn validates_uint64_range() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": { "val": { "TypeDef:Uint64": true, "type": "integer" } },
"properties": { "val": { "AlkType:Uint64": true, "type": "integer" } },
"required": ["val"]
});
let validator = validator_for(&schema);
@@ -456,11 +456,11 @@ mod tests {
#[test]
fn validates_float_finiteness() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"f32": { "TypeDef:Float32": true, "type": "number" },
"f64": { "TypeDef:Float64": true, "type": "number" }
"f32": { "AlkType:Float32": true, "type": "number" },
"f64": { "AlkType:Float64": true, "type": "number" }
},
"required": ["f32", "f64"]
});
@@ -473,9 +473,9 @@ mod tests {
#[test]
fn validates_boolean() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": { "active": { "TypeDef:Boolean": true, "type": "boolean" } },
"properties": { "active": { "AlkType:Boolean": true, "type": "boolean" } },
"required": ["active"]
});
let validator = validator_for(&schema);
@@ -487,10 +487,10 @@ mod tests {
#[test]
fn validates_string_and_maxlength() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"name": { "TypeDef:String": true, "type": "string", "maxLength": 5 }
"name": { "AlkType:String": true, "type": "string", "maxLength": 5 }
},
"required": ["name"]
});
@@ -504,10 +504,10 @@ mod tests {
#[test]
fn validates_bytes_and_maxlength() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"blob": { "TypeDef:Bytes": true, "type": "string", "maxLength": 4 }
"blob": { "AlkType:Bytes": true, "type": "string", "maxLength": 4 }
},
"required": ["blob"]
});
@@ -520,11 +520,11 @@ mod tests {
#[test]
fn enum_validator_is_noop_and_builtin_enum_handles_membership() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"status": {
"TypeDef:Enum": true,
"AlkType:Enum": true,
"type": "string",
"enum": ["ok", "error", "pending"]
}
@@ -540,10 +540,10 @@ mod tests {
#[test]
fn validates_timestamp_rfc3339() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"created_at": { "TypeDef:Timestamp": true, "type": "string" }
"created_at": { "AlkType:Timestamp": true, "type": "string" }
},
"required": ["created_at"]
});
@@ -557,13 +557,13 @@ mod tests {
#[test]
fn validates_array_type() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"items": {
"TypeDef:Array": true,
"AlkType:Array": true,
"type": "array",
"items": { "TypeDef:Uint8": true, "type": "integer" }
"items": { "AlkType:Uint8": true, "type": "integer" }
}
},
"required": ["items"]
@@ -576,13 +576,13 @@ mod tests {
#[test]
fn validates_record_type() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"counts": {
"TypeDef:Record": true,
"AlkType:Record": true,
"type": "object",
"additionalProperties": { "TypeDef:Uint32": true, "type": "integer" }
"additionalProperties": { "AlkType:Uint32": true, "type": "integer" }
}
},
"required": ["counts"]
@@ -595,11 +595,11 @@ mod tests {
#[test]
fn validates_union_type() {
let schema = json!({
"TypeDef:Struct": true,
"AlkType:Struct": true,
"type": "object",
"properties": {
"packet": {
"TypeDef:Union": true,
"AlkType:Union": true,
"type": "object",
"properties": {
"type": { "type": "string" }
@@ -616,15 +616,15 @@ mod tests {
#[test]
fn build_validator_returns_schema_error_for_malformed_keyword() {
let schema = json!({"TypeDef:Uint32": "not-a-bool"});
let schema = json!({"AlkType:Uint32": "not-a-bool"});
let err = build_validator(&schema).expect_err("should fail");
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
}
#[test]
fn build_validator_maps_build_error_to_typedef_error() {
fn build_validator_maps_build_error_to_alktype_error() {
let schema = json!([1, 2, 3]);
let err = build_validator(&schema).expect_err("schema must be an object");
assert!(matches!(err, TypedefError::Schema(_)), "got {err:?}");
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
}
}