diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 8c082de..6f974dc 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -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). | diff --git a/docs/architecture/bast-format.md b/docs/architecture/bast-format.md index 1cb20ab..50e17ea 100644 --- a/docs/architecture/bast-format.md +++ b/docs/architecture/bast-format.md @@ -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 | diff --git a/docs/architecture/builder.md b/docs/architecture/builder.md index 49504d7..28da63f 100644 --- a/docs/architecture/builder.md +++ b/docs/architecture/builder.md @@ -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() diff --git a/docs/architecture/data-access.md b/docs/architecture/data-access.md index dd9d4da..621d69e 100644 --- a/docs/architecture/data-access.md +++ b/docs/architecture/data-access.md @@ -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. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index a9a6fab..cf9c401 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -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). diff --git a/docs/architecture/schema-layer.md b/docs/architecture/schema-layer.md index 2042149..bd55712 100644 --- a/docs/architecture/schema-layer.md +++ b/docs/architecture/schema-layer.md @@ -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 diff --git a/docs/architecture/validation.md b/docs/architecture/validation.md index 95b8c92..463b072 100644 --- a/docs/architecture/validation.md +++ b/docs/architecture/validation.md @@ -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 | diff --git a/src/bast.rs b/src/bast.rs index fdfbfa8..73c8702 100644 --- a/src/bast.rs +++ b/src/bast.rs @@ -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] diff --git a/src/bast_meta.rs b/src/bast_meta.rs index 7f06023..000242a 100644 --- a/src/bast_meta.rs +++ b/src/bast_meta.rs @@ -92,7 +92,7 @@ pub static BAST_META_SCHEMA: LazyLock = 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}"); } diff --git a/src/bast_validation.rs b/src/bast_validation.rs index c8f22ba..93d9e48 100644 --- a/src/bast_validation.rs +++ b/src/bast_validation.rs @@ -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) -> 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) -> 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::().is_ok_and(|y| y > 0) - && date_parts[1] - .parse::() - .is_ok_and(|m| (1..=12).contains(&m)) - && date_parts[2] - .parse::() - .is_ok_and(|d| (1..=31).contains(&d)) - && time_parts[0].parse::().is_ok_and(|h| h <= 23) - && time_parts[1].parse::().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] diff --git a/src/builder.rs b/src/builder.rs index d8b8831..9997ea6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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 ------------------------------------------------------- diff --git a/src/engine.rs b/src/engine.rs index 3e064bd..fa2a13a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -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!({ diff --git a/src/layout_builder.rs b/src/layout_builder.rs index 87c1266..7476478 100644 --- a/src/layout_builder.rs +++ b/src/layout_builder.rs @@ -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!({ diff --git a/src/materialize.rs b/src/materialize.rs index 0e74af1..948ad9c 100644 --- a/src/materialize.rs +++ b/src/materialize.rs @@ -208,15 +208,6 @@ fn materialize_typeref_packed( let arr: Vec = 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}"), diff --git a/src/offset_map.rs b/src/offset_map.rs index 970a86e..db305dd 100644 --- a/src/offset_map.rs +++ b/src/offset_map.rs @@ -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. diff --git a/src/schema.rs b/src/schema.rs index 229083b..d4242ac 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -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()); } } \ No newline at end of file diff --git a/src/sequential_reader.rs b/src/sequential_reader.rs index ebe81ea..1910467 100644 --- a/src/sequential_reader.rs +++ b/src/sequential_reader.rs @@ -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!({