Remove the Timestamp kind

The Timestamp kind was a residual from an early research reference. It
was byte-identical to String everywhere (length-prefixed UTF-8) and its
only distinguishing behavior was a hand-rolled non-strict RFC 3339 check
that the docs admitted was incomplete (Feb 31 passes, seconds range
unchecked, no leap seconds). JSON-level timestamp validation is
jsonschema's job (format: date-time on the validate_json path), not
alktype's.

Removes the AlkTypeKind::Timestamp variant, its to_bast_str/from_bast_str
mapping, the builder's Schema::timestamp() constructor, the
validate_timestamp/is_rfc3339_timestamp validator arms, and the
materializer/reader/engine timestamp arms. Updates the meta-schema
primitive enum (14 -> 13), the spec docs (bast-format.md, schema-layer.md,
builder.md, validation.md, overview.md, data-access.md, README.md), and
the kind-count references (19 -> 18).

Verification: cargo test --release (407 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
This commit is contained in:
deepseek-v4-pro committed 2026-08-16 09:12:33 +00:00
1 parent 62ed009281
commit 510553d800
17 files changed
+38 -225

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ format definition; the engine is generic.
|----------|--------|-------------|
| [overview.md](overview.md) | accepted | Crate purpose, "schema is the format" principle, dependencies, consumers, scope boundaries |
| [`bast-format.md`](bast-format.md) | accepted | **Normative BAST format specification.** Meta-schema, TypeRef, TypeDef shapes (Struct/Union/Enum/FieldDef), examples, validation model. The format the engine consumes. |
| [schema-layer.md](schema-layer.md) | accepted | The BAST parser (`src/bast.rs`) — the typed tree (`BastDoc`/`BastDef`/`BastType`/…) every engine module walks, the 19 BAST kinds, the `AlkTypeKind` enum, and the foundational annotation types. |
| [schema-layer.md](schema-layer.md) | accepted | The BAST parser (`src/bast.rs`) — the typed tree (`BastDoc`/`BastDef`/`BastType`/…) every engine module walks, the 18 BAST kinds, the `AlkTypeKind` enum, and the foundational annotation types. |
| [layout-engine.md](layout-engine.md) | draft | Offset computation, the two layout modes (packed sequential vs aligned static), alignment, endianness, variable-length handling |
| [data-access.md](data-access.md) | draft | Read/write functions, TUnion dispatch, field paths, zero-copy access, length-prefix reading |
| [validation.md](validation.md) | accepted | The two-validator model (BAST-native for `validate_bytes`, standard `jsonschema` for `validate_json`), `AlkTypeError`, load-time vs access-time validation, `AlkTypeEngine` as the compiled form of a BAST document (ADR-010, ADR-VAL-SPLIT). |
+3 -5
View File
@@ -140,7 +140,7 @@ These are different validators for different inputs.
"int8", "int16", "int32", "int64",
"uint8", "uint16", "uint32", "uint64",
"float32", "float64",
"bool", "string", "bytes", "timestamp"
"bool", "string", "bytes"
]
},
{
@@ -309,7 +309,7 @@ are convenient for one-off nested types.
### Primitives
The 14 primitive `kind` strings map to the unchanged `AlkTypeKind`
The 13 primitive `kind` strings map to the unchanged `AlkTypeKind`
variants (D-BAST-002 — lowercase strings, PascalCase enum variants):
| BAST kind | `AlkTypeKind` | Rust type | Size | Category |
@@ -327,7 +327,6 @@ variants (D-BAST-002 — lowercase strings, PascalCase enum variants):
| `bool` | `Boolean` | `bool` (`0x00`=false, `0x01`=true) | 1 | fixed |
| `string` | `String` | length-prefixed UTF-8 | variable | variable |
| `bytes` | `Bytes` | length-prefixed raw bytes | variable | variable |
| `timestamp` | `Timestamp` | length-prefixed RFC 3339 string | variable | variable |
`int64`/`uint64` are alktype additions (not in TypeBox's `typedef.ts`),
required by SFTP `offset: u64` and metatensor `data_offsets`. JSON
@@ -457,7 +456,7 @@ override). In little-endian mode, `u32::from_le_bytes`; in big-endian
mode, `u32::from_be_bytes`. Ensures SFTP consumers (big-endian) have
consistent byte order for field values and length prefixes.
Applies to all variable-length types: `string`, `bytes`, `timestamp`,
Applies to all variable-length types: `string`, `bytes`,
`record`, and arrays of variable-length elements.
## Endianness
@@ -541,7 +540,6 @@ BAST type tree that checks exactly these:
| Float finiteness (Float32/64) | `validate_float` with `as_f64().is_finite()` |
| String `maxLength` (byte length) | `check_string` reads the field-level `maxLength` |
| Bytes `maxLength` (array length) | `check_bytes` accepts the `Value::Array` form (the materializer emits bytes as an array of u8) |
| RFC 3339 timestamp shape | `validate_timestamp` — same non-strict check as v0.1.0 |
| Enum index bounds | `validate_enum` checks `idx < values.len()` — **fixes the v0.1.0 dead constraint** |
| Union variant dispatch | `validate_union` reads `__discriminator`, looks up the variant, recurses via `validate_typeref` |
| Struct fields | `validate_struct` walks `fields`, requires each declared field present, recurses |
+3 -4
View File
@@ -33,7 +33,7 @@ constructor used (D-BAST-008):
The builder covers:
- All 19 BAST kinds (binary-layout schemas) — see
- All 18 BAST kinds (binary-layout schemas) — see
[schema-layer.md](schema-layer.md) for the kinds and
[`bast-format.md`](bast-format.md) for the format.
- All standard JSON Schema keywords needed for operation payload
@@ -113,7 +113,7 @@ constraints; `.build()` produces `Value`.
#### BAST kind constructors
One constructor per `AlkTypeKind` variant (see [schema-layer.md](schema-layer.md)
§"The 19 BAST Kinds"):
§"The 18 BAST Kinds"):
```rust
impl Schema {
@@ -135,7 +135,6 @@ impl Schema {
// Variable-length kinds
pub fn string() -> Self;
pub fn bytes() -> Self;
pub fn timestamp() -> Self;
// Composite kinds
pub fn struct_() -> Self; // fields added via .field()
pub fn union_(disc: Discriminator) -> Self; // variants via .mapping()
@@ -150,7 +149,7 @@ Composite constructors produce the BAST object form.
**`enum_of`** produces a BAST enum TypeDef (`{ "kind": "enum", "values":
[...] }`); declaration order is the index order — see
[schema-layer.md](schema-layer.md) §"The 19 BAST Kinds"):
[schema-layer.md](schema-layer.md) §"The 18 BAST Kinds"):
```rust
Schema::enum_of(&["read", "write", "execute"]).build()
+1 -1
View File
@@ -100,7 +100,7 @@ impl SequentialReader {
```
`read_field`/`write_field` on `AlkTypeEngine` work for the fixed-size
primitive kinds and the length-prefixed `String`/`Bytes`/`Timestamp`
primitive kinds and the length-prefixed `String`/`Bytes`
fields. Composite kinds (`Struct`, `Union`, `Array`, `Record`) return a
`FieldValue` carrying a layout descriptor (byte range, variant start,
or array stride) for the consumer to recurse on — see §"FieldValue" above.
+3 -3
View File
@@ -27,8 +27,8 @@ three capabilities:
3. **Validation** — two validators for two input types:
- `validate_bytes(&[u8])` uses the BAST-native validator (a recursive
walker over the BAST type tree) to check the value-domain
constraints the materializer doesn't (integer ranges, `maxLength`,
timestamp shape, enum index bounds, union variant constraints).
constraints the materializer doesn't (integer ranges, `maxLength`,
enum index bounds, union variant constraints).
- `validate_json(&Value)` uses a standard `jsonschema::Validator`
compiled from a consumer-provided JSON Schema (BAST is not involved
— BAST describes bytes, not JSON shape).
@@ -194,7 +194,7 @@ and [ADR-BAST](decisions/bast-bast-format.md).
## Architecture (component pointers)
- **[schema-layer.md](schema-layer.md)** — the BAST parser (the typed
surface every engine module walks), the 19 BAST kinds, the
surface every engine module walks), the 18 BAST kinds, the
`AlkTypeKind` enum, and the foundational annotation types.
- **[`bast-format.md`](bast-format.md)** — the normative BAST format
specification (meta-schema, TypeRef, examples, validation model).
+3 -4
View File
@@ -18,7 +18,7 @@ validation model). This document describes the *implementation* — the
typed parser in `src/bast.rs` and the foundational `AlkTypeKind` enum
in `src/schema.rs` — and points at the format spec for shape details.
## The 19 BAST Kinds
## The 18 BAST Kinds
BAST uses lowercase `kind` strings (`"uint32"`, `"struct"`, `"union"`,
etc.). The engine represents them as the `AlkTypeKind` Rust enum — one
@@ -41,7 +41,6 @@ comparison at every field access.
| `bool` | `Boolean` | `bool` (`0x00`=false, `0x01`=true) | 1 | fixed |
| `string` | `String` | length-prefixed UTF-8 | variable | variable |
| `bytes` | `Bytes` | length-prefixed raw bytes | variable | variable |
| `timestamp` | `Timestamp` | length-prefixed RFC 3339 string | variable | variable |
| `struct` | `Struct` | record of fields | sum of field sizes | composite |
| `union` | `Union` | tagged union | discriminator + variant | composite |
| `array` | `Array` | repeated element | count × element size | composite |
@@ -63,7 +62,7 @@ pub enum AlkTypeKind {
Uint8, Uint16, Uint32, Uint64,
Float32, Float64,
Boolean, Enum,
String, Bytes, Timestamp,
String, Bytes,
Struct, Union, Array, Record,
}
```
@@ -78,7 +77,7 @@ The enum carries the kind's binary-layout metadata as inherent methods:
| `natural_alignment(self)` | `usize` | 1 for u8/i8/bool, 2 for u16/i16, 4 for u32/i32/f32/enum, 8 for u64/i64/f64, 4 for variable-length (the u32 length prefix), 1 for struct/union/array |
| `is_fixed_size(self)` | `bool` | True for the 12 fixed-size primitive kinds |
| `is_composite(self)` | `bool` | True for Struct, Union, Array, Record |
| `is_variable_length(self)` | `bool` | True for String, Bytes, Timestamp, Record |
| `is_variable_length(self)` | `bool` | True for String, Bytes, Record |
| `needs_endian(self)` | `bool` | True for kinds whose read/write takes an `Endian` parameter |
`AlkTypeKind` implements `Display`, backed by `to_bast_str` so the
-1
View File
@@ -48,7 +48,6 @@ tree ([`crate::bast::BastDoc`]/[`BastType`]) that checks exactly these:
| Float finiteness (Float32/64) | `validate_float` with `as_f64().is_finite()` |
| String `maxLength` (byte length) | `check_string` reads the field-level `maxLength` |
| Bytes `maxLength` (array length) | `check_bytes` accepts the `Value::Array` form (the materializer emits bytes as an array of u8) |
| RFC 3339 timestamp shape | `validate_timestamp` — non-strict check (matching v0.1.0) |
| Enum index bounds | `validate_enum` checks `idx < values.len()` — **fixes the v0.1.0 dead constraint** |
| Union variant dispatch | `validate_union` reads `__discriminator`, looks up the variant, recurses via `validate_typeref` |
| Struct fields | `validate_struct` walks `fields`, requires each declared field present, recurses |
+2 -2
View File
@@ -1139,7 +1139,7 @@ mod tests {
{ "name": "a", "kind": "int8" },
{ "name": "b", "kind": "uint64" },
{ "name": "c", "kind": "bool" },
{ "name": "d", "kind": "timestamp" }
{ "name": "d", "kind": "bytes" }
] } }
});
let d = doc_from(&root, "S");
@@ -1150,7 +1150,7 @@ mod tests {
assert!(matches!(s.fields()[0].ty(), BastType::Primitive(AlkTypeKind::Int8)));
assert!(matches!(s.fields()[1].ty(), BastType::Primitive(AlkTypeKind::Uint64)));
assert!(matches!(s.fields()[2].ty(), BastType::Primitive(AlkTypeKind::Boolean)));
assert!(matches!(s.fields()[3].ty(), BastType::Primitive(AlkTypeKind::Timestamp)));
assert!(matches!(s.fields()[3].ty(), BastType::Primitive(AlkTypeKind::Bytes)));
}
#[test]
+4 -4
View File
@@ -92,7 +92,7 @@ pub static BAST_META_SCHEMA: LazyLock<Value> = LazyLock::new(|| {
"int8", "int16", "int32", "int64",
"uint8", "uint16", "uint32", "uint64",
"float32", "float64",
"bool", "string", "bytes", "timestamp"
"bool", "string", "bytes"
]
},
{
@@ -253,7 +253,7 @@ mod tests {
}
#[test]
fn meta_schema_typeref_enumerates_all_14_primitives() {
fn meta_schema_typeref_enumerates_all_13_primitives() {
let typeref_oneof = BAST_META_SCHEMA["$defs"]["TypeRef"]["oneOf"]
.as_array()
.expect("TypeRef.oneOf is array");
@@ -265,12 +265,12 @@ mod tests {
.as_array()
.expect("primitive enum is array");
let strs: Vec<&str> = primitives.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(strs.len(), 14);
assert_eq!(strs.len(), 13);
for expected in [
"int8", "int16", "int32", "int64",
"uint8", "uint16", "uint32", "uint64",
"float32", "float64",
"bool", "string", "bytes", "timestamp",
"bool", "string", "bytes",
] {
assert!(strs.contains(&expected), "missing primitive {expected}");
}
+1 -69
View File
@@ -29,7 +29,6 @@
//! | Float finiteness (Float32/64) | `validate_float` |
//! | String `maxLength` (byte length) | `check_string` reads [`BastField::max_length`](crate::bast::BastField::max_length) |
//! | Bytes `maxLength` (array length) | `check_bytes` |
//! | RFC 3339 timestamp shape | `validate_timestamp` (non-strict, matching v0.1.0) |
//! | Enum index bounds | `validate_enum` — **fixes the v0.1.0 dead constraint** |
//! | Union variant dispatch | `validate_union` reads `__discriminator`, resolves the variant, recurses |
//! | Struct fields | `validate_struct` walks `fields`, requires each declared field present, recurses |
@@ -71,7 +70,7 @@ const DISCRIMINATOR_KEY: &str = "__discriminator";
/// from the bytes, this walker enforces the value-domain constraints
/// expressed in the BAST document. The materializer already guarantees
/// structural correctness; the validator only checks what the bytes
/// alone can't tell you (integer ranges, `maxLength`, timestamp shape,
/// alone can't tell you (integer ranges, `maxLength`,
/// enum index bounds, union variant constraints).
///
/// Returns `Err(AlkTypeError::Validation(...))` on the first violated
@@ -121,7 +120,6 @@ fn validate_typeref(
BastType::Primitive(AlkTypeKind::Bytes) => {
check_bytes(value, path, field.and_then(|f| f.max_length()))
}
BastType::Primitive(AlkTypeKind::Timestamp) => validate_timestamp(value, path),
BastType::Enum(e) => validate_enum(value, path, e),
BastType::Struct(s) => validate_struct(doc, s, value, path),
BastType::Union(u) => validate_union(doc, u, value, path),
@@ -239,13 +237,6 @@ fn check_bytes(value: &Value, path: &str, max_length: Option<usize>) -> Result<(
Ok(())
}
fn validate_timestamp(value: &Value, path: &str) -> Result<(), AlkTypeError> {
match value.as_str() {
Some(s) if is_rfc3339_timestamp(s) => Ok(()),
_ => Err(validation_err(path, "expected an RFC 3339 timestamp string")),
}
}
/// Enum validation on the bytes path: the materializer emits a numeric
/// index (`Value::Number`), and the constraint is that the index is
/// within the `values` array bounds (`0..len-1`).
@@ -381,48 +372,6 @@ fn validation_err(path: &str, reason: impl Into<String>) -> AlkTypeError {
AlkTypeError::Validation(jsonschema::ValidationError::custom(msg))
}
/// Non-strict RFC 3339 / ISO 8601 datetime check — same shape as the
/// v0.1.0 `TimestampValidator` so behavior is identical. Structural
/// sanity, not strict validation (Feb 31 passes; seconds range isn't
/// checked; leap seconds aren't handled). Strict RFC 3339 validation is
/// the consumer's responsibility if they need it (ADR-003, review #002
/// N1).
fn is_rfc3339_timestamp(s: &str) -> bool {
let parts: Vec<&str> = s.splitn(2, 'T').collect();
if parts.len() != 2 {
return false;
}
let date_parts: Vec<&str> = parts[0].split('-').collect();
if date_parts.len() != 3 {
return false;
}
let time_part = parts[1];
let time_clean = if let Some(pos) = time_part.find(['Z', '+']) {
&time_part[..pos]
} else if let Some(pos) = time_part.rfind('-') {
if pos >= 8 {
&time_part[..pos]
} else {
time_part
}
} else {
time_part
};
let time_parts: Vec<&str> = time_clean.split(':').collect();
if time_parts.len() < 2 || time_parts.len() > 3 {
return false;
}
date_parts[0].parse::<u16>().is_ok_and(|y| y > 0)
&& date_parts[1]
.parse::<u8>()
.is_ok_and(|m| (1..=12).contains(&m))
&& date_parts[2]
.parse::<u8>()
.is_ok_and(|d| (1..=31).contains(&d))
&& time_parts[0].parse::<u8>().is_ok_and(|h| h <= 23)
&& time_parts[1].parse::<u8>().is_ok_and(|m| m <= 59)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -564,23 +513,6 @@ mod tests {
assert!(validate_value(&d, &json!({"blob": [65, 255]})).is_ok());
}
// ----- Timestamp -------------------------------------------------------
#[test]
fn timestamp_valid_and_invalid() {
let root = json!({
"$defs": { "S": { "kind": "struct", "fields": [
{ "name": "ts", "kind": "timestamp" }
] } }
});
let d = doc_from(&root, "S");
assert!(materialize_and_validate(&d, &prefixed_str_le("2026-07-20T15:30:00Z")).is_ok());
assert!(materialize_and_validate(&d, &prefixed_str_le("2026-07-20T15:30:00")).is_ok());
assert!(materialize_and_validate(&d, &prefixed_str_le("2026-07-20T15:30:00+02:00")).is_ok());
let err = materialize_and_validate(&d, &prefixed_str_le("not-a-date")).unwrap_err();
assert!(matches!(err, AlkTypeError::Validation(_)), "got {err:?}");
}
// ----- Enum index bounds (the v0.1.0 dead-constraint fix) -------------
#[test]
-5
View File
@@ -170,10 +170,6 @@ impl Schema {
pub fn bytes() -> Self {
Self::primitive(AlkTypeKind::Bytes)
}
/// BAST `timestamp` (length-prefixed RFC 3339 string).
pub fn timestamp() -> Self {
Self::primitive(AlkTypeKind::Timestamp)
}
fn primitive(kind: AlkTypeKind) -> Self {
Self {
@@ -837,7 +833,6 @@ mod tests {
assert_eq!(Schema::boolean().build(), json!("bool"));
assert_eq!(Schema::string().build(), json!("string"));
assert_eq!(Schema::bytes().build(), json!("bytes"));
assert_eq!(Schema::timestamp().build(), json!("timestamp"));
}
// ----- Struct -------------------------------------------------------
+3 -29
View File
@@ -259,7 +259,7 @@ impl AlkTypeEngine {
/// and then validating that `Value` against the BAST-native validator
/// — a recursive walker over the BAST type tree that checks the
/// value-domain constraints the materializer does not (integer
/// ranges, `maxLength`, timestamp shape, enum index bounds, union
/// ranges, `maxLength`, enum index bounds, union
/// variant constraints). Decided in D-BAST-006; see
/// [bast-format.md §Validation Model](../../docs/architecture/bast-format.md#validation-model).
///
@@ -296,7 +296,7 @@ impl AlkTypeEngine {
/// Looks up the field's byte range in the [`OffsetMap`] and reads the
/// appropriate type using the [`crate::data_access`] functions. Works
/// for fixed-size primitive kinds and length-prefixed `String`/
/// `Bytes`/`Timestamp` fields.
/// `Bytes` fields.
///
/// Returns an error if compiled in packed mode — use
/// [`AlkTypeEngine::sequential_reader`] for packed mode. Also
@@ -409,10 +409,6 @@ impl AlkTypeEngine {
};
Ok(FieldValue::Bytes(v))
}
AlkTypeKind::Timestamp => {
let v = data_access::read_string(buffer, range.start, field_path, endian)?;
Ok(FieldValue::String(v))
}
AlkTypeKind::Struct => Ok(FieldValue::Struct {
start: range.start,
end: range.end,
@@ -433,7 +429,7 @@ impl AlkTypeEngine {
/// Looks up the field's byte range in the [`OffsetMap`] and writes the
/// appropriate type using the [`crate::data_access`] functions. Works
/// for fixed-size primitive kinds and length-prefixed `String`/
/// `Bytes`/`Timestamp` fields.
/// `Bytes` fields.
///
/// Returns an error if compiled in packed mode — use
/// [`AlkTypeEngine::layout_builder`] for packed mode. Also returns
@@ -1080,28 +1076,6 @@ mod tests {
}
}
#[test]
fn read_field_aligned_reads_timestamp_as_string() {
let doc = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [ { "name": "ts", "kind": "timestamp" } ]
}
}
});
let engine = AlkTypeEngine::compile(&doc, "S", LayoutMode::Aligned, None).expect("compile");
let mut buf = vec![0u8; 32];
let stamp = "2026-07-20T15:30:00Z";
let len_bytes = (stamp.len() as u32).to_le_bytes();
buf[0..4].copy_from_slice(&len_bytes);
buf[4..4 + stamp.len()].copy_from_slice(stamp.as_bytes());
match engine.read_field(&buf, "ts").unwrap() {
FieldValue::String(s) => assert_eq!(s, stamp),
other => panic!("expected String, got {other:?}"),
}
}
#[test]
fn read_field_aligned_honors_field_level_endian_override() {
let doc = json!({
+3 -27
View File
@@ -15,8 +15,8 @@
//! known byte size; the offset advances by the size. No alignment
//! padding is inserted (the `u32` at offset 1 is unaligned — correct
//! for protocol wire formats).
//! - **Variable-length fields** (`string`, `bytes`, `timestamp`,
//! `record`): always inline length-prefixed in packed mode. The
//! - **Variable-length fields** (`string`, `bytes`, `record`): always
//! inline length-prefixed in packed mode. The
//! 4-byte length prefix is recorded at the current offset; the offset
//! advances by `4 + data_size` where `data_size` comes from `var_sizes`
//! keyed by the field's dotted path.
@@ -291,7 +291,7 @@ impl<'d> BuildCtx<'d> {
}
/// Compute the layout for a variable-length field (String/Bytes/
/// Timestamp/Record). Always inline length-prefixed in packed mode.
/// Record). Always inline length-prefixed in packed mode.
fn walk_variable(
&mut self,
field_path: &str,
@@ -702,30 +702,6 @@ mod tests {
assert_eq!(layout.total_size(), 7);
}
#[test]
fn timestamp_field_uses_var_sizes() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "ts", "kind": "timestamp" }
]
}
}
});
let layout = build(&root, "S", &var_sizes(&[("ts", 20)]));
assert_eq!(
layout.get("ts"),
Some(&FieldPosition {
offset: 0,
size: 4,
kind: AlkTypeKind::Timestamp
})
);
assert_eq!(layout.total_size(), 24);
}
#[test]
fn record_field_uses_var_sizes() {
let root = json!({
+3 -14
View File
@@ -208,15 +208,6 @@ fn materialize_typeref_packed(
let arr: Vec<Value> = b.iter().map(|&byte| Value::from(u32::from(byte))).collect();
Ok(Value::Array(arr))
}
BastType::Primitive(AlkTypeKind::Timestamp) => {
let s = data_access::read_string(buffer, *offset, field_path, endian)?;
let len = U32_SIZE + s.len();
*offset = offset.checked_add(len).ok_or_else(|| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("timestamp offset {len} overflows usize"),
})?;
Ok(Value::String(s.to_string()))
}
BastType::Struct(s) => {
materialize_struct_packed(doc, s, field_path, endian, buffer, offset)
}
@@ -556,7 +547,7 @@ fn materialize_array_aligned(
Ok(Value::Array(arr))
}
/// Materialize a variable-length leaf (String/Bytes/Timestamp) in aligned
/// Materialize a variable-length leaf (String/Bytes) in aligned
/// mode, dispatching on the field's encoding strategy:
///
/// - `offset-indirect`: an 8-byte `{offset, length}` pair at `start`,
@@ -576,8 +567,7 @@ fn materialize_variable_aligned(
let start = range.start;
match field.encoding() {
VariableEncoding::OffsetIndirect => match ty {
BastType::Primitive(AlkTypeKind::String)
| BastType::Primitive(AlkTypeKind::Timestamp) => {
BastType::Primitive(AlkTypeKind::String) => {
let s = data_access::read_string_indirect(buffer, start, field_path, endian)?;
Ok(Value::String(s.to_string()))
}
@@ -603,8 +593,7 @@ fn materialize_variable_aligned(
.unwrap_or(0);
let trimmed = &data[..trimmed_len];
match ty {
BastType::Primitive(AlkTypeKind::String)
| BastType::Primitive(AlkTypeKind::Timestamp) => {
BastType::Primitive(AlkTypeKind::String) => {
let s = std::str::from_utf8(trimmed).map_err(|e| AlkTypeError::Access {
field_path: field_path.to_string(),
reason: format!("invalid UTF-8 in maxLength string: {e}"),
+1 -1
View File
@@ -388,7 +388,7 @@ impl<'d> ComputeCtx<'d> {
Ok(FieldLayout { align: array_align })
}
/// Compute the layout for a variable-length field (String/Bytes/Record/Timestamp).
/// Compute the layout for a variable-length field (String/Bytes/Record).
///
/// In aligned static mode, three strategies are supported:
/// - `maxLength` reservation: `maxLength` bytes at a fixed offset.
+7 -24
View File
@@ -16,7 +16,7 @@ use std::fmt;
pub(crate) const U32_SIZE: usize = 4;
pub(crate) const DISCRIMINATOR_PATH: &str = "__discriminator";
/// The 19 BAST kinds recognized by the engine.
/// The 18 BAST kinds recognized by the engine.
///
/// Each variant corresponds to a lowercase BAST kind string
/// ([`AlkTypeKind::to_bast_str`], e.g. `"uint32"` ↔ `Uint32`). The enum
@@ -42,7 +42,6 @@ pub enum AlkTypeKind {
Union,
Array,
Record,
Timestamp,
}
impl AlkTypeKind {
@@ -60,8 +59,7 @@ impl AlkTypeKind {
| AlkTypeKind::Struct
| AlkTypeKind::Union
| AlkTypeKind::Array
| AlkTypeKind::Record
| AlkTypeKind::Timestamp => None,
| AlkTypeKind::Record => None,
}
}
@@ -79,8 +77,7 @@ impl AlkTypeKind {
AlkTypeKind::Float64 | AlkTypeKind::Int64 | AlkTypeKind::Uint64 => 8,
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Record
| AlkTypeKind::Timestamp => 4,
| AlkTypeKind::Record => 4,
AlkTypeKind::Struct | AlkTypeKind::Union | AlkTypeKind::Array => 1,
}
}
@@ -119,7 +116,6 @@ impl AlkTypeKind {
| AlkTypeKind::Enum
| AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Timestamp
)
}
@@ -131,13 +127,12 @@ impl AlkTypeKind {
)
}
/// Returns `true` for variable-length kinds (String, Bytes, Timestamp, Record).
/// Returns `true` for variable-length kinds (String, Bytes, Record).
pub fn is_variable_length(self) -> bool {
matches!(
self,
AlkTypeKind::String
| AlkTypeKind::Bytes
| AlkTypeKind::Timestamp
| AlkTypeKind::Record
)
}
@@ -161,7 +156,6 @@ impl AlkTypeKind {
AlkTypeKind::Boolean => "bool",
AlkTypeKind::String => "string",
AlkTypeKind::Bytes => "bytes",
AlkTypeKind::Timestamp => "timestamp",
AlkTypeKind::Enum => "enum",
AlkTypeKind::Struct => "struct",
AlkTypeKind::Union => "union",
@@ -189,7 +183,6 @@ impl AlkTypeKind {
"bool" => Ok(AlkTypeKind::Boolean),
"string" => Ok(AlkTypeKind::String),
"bytes" => Ok(AlkTypeKind::Bytes),
"timestamp" => Ok(AlkTypeKind::Timestamp),
"enum" => Ok(AlkTypeKind::Enum),
"struct" => Ok(AlkTypeKind::Struct),
"union" => Ok(AlkTypeKind::Union),
@@ -255,7 +248,6 @@ mod tests {
AlkTypeKind::Union,
AlkTypeKind::Array,
AlkTypeKind::Record,
AlkTypeKind::Timestamp,
] {
assert_eq!(kind.type_size(), None, "failed for {kind:?}");
}
@@ -278,7 +270,6 @@ mod tests {
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);
@@ -309,7 +300,6 @@ mod tests {
AlkTypeKind::Union,
AlkTypeKind::Array,
AlkTypeKind::Record,
AlkTypeKind::Timestamp,
] {
assert!(!kind.is_fixed_size(), "expected variable: {kind:?}");
}
@@ -329,7 +319,6 @@ mod tests {
AlkTypeKind::Enum,
AlkTypeKind::String,
AlkTypeKind::Bytes,
AlkTypeKind::Timestamp,
] {
assert!(kind.needs_endian(), "expected needs_endian: {kind:?}");
}
@@ -371,14 +360,13 @@ mod tests {
AlkTypeKind::Enum,
AlkTypeKind::String,
AlkTypeKind::Bytes,
AlkTypeKind::Timestamp,
] {
assert!(!kind.is_composite(), "expected not composite: {kind:?}");
}
}
#[test]
fn to_bast_str_covers_all_19_variants() {
fn to_bast_str_covers_all_18_variants() {
let cases: &[(AlkTypeKind, &str)] = &[
(AlkTypeKind::Int8, "int8"),
(AlkTypeKind::Int16, "int16"),
@@ -393,14 +381,13 @@ mod tests {
(AlkTypeKind::Boolean, "bool"),
(AlkTypeKind::String, "string"),
(AlkTypeKind::Bytes, "bytes"),
(AlkTypeKind::Timestamp, "timestamp"),
(AlkTypeKind::Enum, "enum"),
(AlkTypeKind::Struct, "struct"),
(AlkTypeKind::Union, "union"),
(AlkTypeKind::Array, "array"),
(AlkTypeKind::Record, "record"),
];
assert_eq!(cases.len(), 19);
assert_eq!(cases.len(), 18);
for (kind, expected) in cases {
assert_eq!(kind.to_bast_str(), *expected);
}
@@ -422,7 +409,6 @@ mod tests {
AlkTypeKind::Boolean,
AlkTypeKind::String,
AlkTypeKind::Bytes,
AlkTypeKind::Timestamp,
AlkTypeKind::Enum,
AlkTypeKind::Struct,
AlkTypeKind::Union,
@@ -449,10 +435,6 @@ mod tests {
AlkTypeKind::from_bast_str("struct"),
Ok(AlkTypeKind::Struct)
));
assert!(matches!(
AlkTypeKind::from_bast_str("timestamp"),
Ok(AlkTypeKind::Timestamp)
));
}
#[test]
@@ -460,6 +442,7 @@ mod tests {
assert!(AlkTypeKind::from_bast_str("uint128").is_err());
assert!(AlkTypeKind::from_bast_str("Uint32").is_err());
assert!(AlkTypeKind::from_bast_str("AlkType:Uint32").is_err());
assert!(AlkTypeKind::from_bast_str("timestamp").is_err());
assert!(AlkTypeKind::from_bast_str("").is_err());
}
}
-31
View File
@@ -359,11 +359,6 @@ fn read_field_value<'a>(
let total = U32_SIZE + b.len();
Ok((FieldValue::Bytes(b), offset + total))
}
BastType::Primitive(AlkTypeKind::Timestamp) => {
let s = data_access::read_string(buffer, offset, field_path, endian)?;
let total = U32_SIZE + s.len();
Ok((FieldValue::String(s), offset + total))
}
BastType::Struct(s) => {
let size = walk_struct_size(doc, s, buffer, offset, endian)?;
let end = offset
@@ -745,11 +740,6 @@ fn read_primitive_value<'a>(
let total = U32_SIZE + b.len();
Ok((FieldValue::Bytes(b), offset + total))
}
AlkTypeKind::Timestamp => {
let s = data_access::read_string(buffer, offset, field_path, endian)?;
let total = U32_SIZE + s.len();
Ok((FieldValue::String(s), offset + total))
}
other => Err(AlkTypeError::Schema(format!(
"{other} is not a primitive readable by read_primitive_value"
))),
@@ -1398,27 +1388,6 @@ mod tests {
assert_eq!(reader.position(), pos);
}
#[test]
fn timestamp_field_reads_as_length_prefixed_string() {
let root = json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [ { "name": "ts", "kind": "timestamp" } ]
}
}
});
let mut buf = vec![0u8; 64];
let stamp = "2026-07-20T15:30:00Z";
let written = write_string(&mut buf, 0, stamp, LE);
let mut reader = reader(&root, "S");
let (name, value) = reader.read_next(&buf).unwrap().unwrap();
assert_eq!(name, "ts");
assert_eq!(value, FieldValue::String(stamp));
assert_eq!(reader.position(), written);
}
#[test]
fn record_field_walks_entries() {
let root = json!({