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:
1 parent
62ed009281
commit
510553d800
17 files changed
+38
-225
No files matched your search
+1
-69
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user