Add BAST-native validator for validate_bytes (step 5)

Replace the jsonschema custom-keyword validator on the bytes path with
a recursive walker over the BAST typed tree. The materializer already
guarantees structural correctness; the validator enforces only the
value-domain constraints expressed in the BAST document.

- New `src/bast_validation.rs`: `validate_value` dispatches on
  `BastType`, resolving `$ref`s lazily via `BastDoc::resolve_typeref`.
  Constraint arms: integer ranges (Int8..Uint64), float finiteness,
  string/bytes `maxLength` (from `BastField::max_length`), RFC 3339
  timestamp shape, enum index bounds, union variant dispatch (recurses
  into the variant, recovering OQ-008 per-variant constraints), struct
  field presence, array count, record values.
- `engine::validate_bytes` now calls `bast_validation::validate_value`
  instead of `self.validator.validate`. The `validator` field is still
  built and used by `validate_json`/`is_valid_json` (step 6 reworks
  those).
- Enum index bounds check (`idx < values.len()`) fixes the v0.1.0 dead
  constraint: the built-in `enum` keyword checked string membership, but
  the materializer emits a numeric index that never matched.
- Errors constructed via `jsonschema::ValidationError::custom` so
  `AlkTypeError::Validation` keeps its payload type uniform with the
  `validate_json` path (D-BAST-009).
- `lib.rs`: add `pub mod bast_validation;` (engine-internal, not
  re-exported in the `pub use` block) and update the module doc.

Verification:
- cargo test --release: 446 pass (369 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo build --target wasm32-unknown-unknown --release: clean
- cargo doc --no-deps: clean
This commit is contained in:
glm-5.2 committed 2026-08-15 12:44:31 +00:00
1 parent 04573e1d86
commit f853dafaf1
3 files changed
+853 -14

No files matched your search

+14 -9
View File
@@ -10,6 +10,7 @@
//! [overview.md](../../docs/architecture/overview.md).
use crate::bast::{BastDefKind, BastDoc, BastStruct, BastType};
use crate::bast_validation;
use crate::data_access;
use crate::error::AlkTypeError;
use crate::layout_builder::LayoutBuilder;
@@ -194,14 +195,17 @@ impl AlkTypeEngine {
/// Validate a binary buffer against the schema by materializing a
/// `serde_json::Value` tree from the bytes (walking the layout engine)
/// and then validating that `Value` against the compiled jsonschema
/// validator. The single-call form of the two-step dance. Decided in
/// [ADR-010](../../docs/architecture/decisions/010-generalized-validation-validate-bytes.md).
/// 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
/// variant constraints). Decided in D-BAST-006; see
/// [bast-format.md §Validation Model](../../docs/architecture/bast-format.md#validation-model).
///
/// Dispatches on the engine's layout mode: packed mode walks
/// sequentially from offset 0; aligned mode reads at offsets from
/// the `OffsetMap`. Both produce the same `Value` form; the
/// validator is mode-agnostic.
/// BAST-native validator is mode-agnostic.
///
/// # Errors
///
@@ -210,8 +214,11 @@ impl AlkTypeEngine {
/// target type). Carries the field path.
/// - [`AlkTypeError::Offset`] if a field is not found in the
/// offset map (aligned mode only).
/// - [`AlkTypeError::Validation`] if the materialized `Value` does
/// not pass the jsonschema validator.
/// - [`AlkTypeError::Validation`] if the materialized `Value`
/// violates a value-domain constraint expressed in the BAST
/// document. The payload is a `jsonschema::ValidationError::custom`
/// (D-BAST-009 — the variant type stays uniform with the
/// `validate_json` path).
pub fn validate_bytes(&self, buffer: &[u8]) -> Result<(), AlkTypeError> {
let doc = BastDoc::new(&self.bast_doc, &self.root_name)?;
let value = match &self.layout {
@@ -220,9 +227,7 @@ impl AlkTypeEngine {
materialize::materialize_aligned(&doc, buffer, offset_map, self.endian)?
}
};
self.validator
.validate(&value)
.map_err(|e| AlkTypeError::Validation(e.to_owned()))
bast_validation::validate_value(&doc, &value)
}
/// Read a field from a buffer at its computed offset (aligned mode).