Validate BAST documents against the meta-schema at compile time

Resolves review #003 finding M3.

- Add bast_meta::validate_bast_doc and call it from AlkTypeEngine::compile
  before parsing. Malformed annotations (unknown endian/encoding strings,
  non-integer align/maxLength, missing required properties) now surface as
  AlkTypeError::Schema instead of being silently tolerated by the parser.
- Align the meta-schema TypeRef with the parser: allow inline struct/union/
  enum as TypeRefs (the parser and builder already accepted them; the
  meta-schema and spec did not). Update bast-format.md TypeRef table and
  the BastType doc comment to the seven-form vocabulary.

Verification: cargo test --release (410 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 08:54:57 +00:00
1 parent e5c7cc1ca2
commit 230345a867
4 files changed
+155 -6

No files matched your search

+72
View File
@@ -124,6 +124,18 @@ pub static BAST_META_SCHEMA: LazyLock<Value> = LazyLock::new(|| {
},
"required": ["kind", "values"],
"additionalProperties": false
},
{
"description": "Inline struct type",
"$ref": "#/$defs/StructDef"
},
{
"description": "Inline union type",
"$ref": "#/$defs/UnionDef"
},
{
"description": "Inline enum type",
"$ref": "#/$defs/EnumDef"
}
]
},
@@ -184,6 +196,26 @@ pub static BAST_META_SCHEMA: LazyLock<Value> = LazyLock::new(|| {
})
});
/// Validate a BAST document against the meta-schema.
///
/// Returns `Ok(())` if the document is well-formed, or
/// [`crate::error::AlkTypeError::Schema`] with the first validation error
/// otherwise.
/// This is the structural gate [`crate::AlkTypeEngine::compile`] applies
/// before parsing — it catches malformed annotations (unknown `endian`/
/// `encoding` strings, non-integer `align`/`maxLength`, missing required
/// properties) that the parser would otherwise silently tolerate.
pub fn validate_bast_doc(doc: &Value) -> Result<(), crate::error::AlkTypeError> {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.map_err(|e| {
crate::error::AlkTypeError::Schema(format!("BAST meta-schema failed to build: {e}"))
})?;
validator.validate(doc).map_err(|e| {
crate::error::AlkTypeError::Schema(format!("BAST document is not well-formed: {e}"))
})
}
#[cfg(test)]
mod tests {
use super::BAST_META_SCHEMA;
@@ -446,4 +478,44 @@ mod tests {
});
assert!(validator.validate(&doc).is_err(), "bare-name ref accepted");
}
#[test]
fn meta_schema_accepts_inline_struct_field() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"S": {
"kind": "struct",
"fields": [
{ "name": "inner", "kind": {
"kind": "struct",
"fields": [ { "name": "x", "kind": "uint16" } ]
} }
]
}
}
});
assert!(validator.validate(&doc).is_ok(), "inline struct field rejected");
}
#[test]
fn meta_schema_accepts_inline_struct_in_union_mapping() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"U": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": {
"5": { "kind": "struct", "fields": [ { "name": "x", "kind": "uint8" } ] }
}
}
}
});
assert!(validator.validate(&doc).is_ok(), "inline struct in union mapping rejected");
}
}