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:
1 parent
1cfb3638d1
commit
5e268e8f47
31 files changed
+1851
-1851
No files matched your search
+33
-33
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+8
-8
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+106
-106
@@ -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
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user