Embed BAST v1 meta-schema as BAST_META_SCHEMA

Step 2 of the BAST pivot. Adds the normative BAST meta-schema
(Draft 2020-12 JSON Schema) as a public serde_json::Value, embedded
at compile time and available for validating BAST document
well-formedness. Copied verbatim from docs/architecture/bast-format.md
§The Meta-Schema.

- New src/bast_meta.rs: BAST_META_SCHEMA static, built lazily via
  LazyLock (the json! macro allocates, so it can't be a const;
  parsed once, reused as &'static Value thereafter).
- src/lib.rs: pub mod bast_meta + re-export BAST_META_SCHEMA.
  Additive public surface.

The meta-schema validates structure (correct $defs shape, known
kind strings, required properties, no additional properties,
$ref restricted to #/$defs/<name>). Value-domain constraints
(maxLength, enum index bounds) are enforced by the BAST-native
validator (step 5), not this meta-schema.

Verification:
- cargo test --release: 418 tests pass (332 lib + 86 integration);
  16 new unit tests exercise the meta-schema against valid and
  invalid BAST documents (missing $defs, unknown kind, additional
  properties, byte-discriminator union, enum, array+count, record,
  $ref, malformed ref, empty enum).
- cargo clippy --all-targets -- -D warnings: clean.
- cargo build --target wasm32-unknown-unknown --release: clean
  (LazyLock + serde_json::json! macro are wasm-safe).
This commit is contained in:
glm-5.2 committed 2026-08-15 11:37:14 +00:00
1 parent 66ab9d7d93
commit 29134789a9
2 files changed
+451

No files matched your search

+449
View File
@@ -0,0 +1,449 @@
//! BAST meta-schema — the standard JSON Schema (Draft 2020-12) that
//! validates the *structure* of BAST documents (is a document
//! well-formed?).
//!
//! This is distinct from the BAST-native validator (step 5), which
//! validates *binary data* against a BAST document (are the bytes a
//! valid instance?). See
//! [`docs/architecture/bast-format.md`](../docs/architecture/bast-format.md)
//! for the normative spec.
//!
//! The meta-schema is embedded at compile time via the `serde_json::json!`
//! macro — no I/O, no feature flags, wasm-clean. It is also published at
//! `https://alk.dev/bast/v1/schema` (the `$id`). Consumers and editors can
//! validate BAST documents against it with any standard JSON Schema
//! validator:
//!
//! ```ignore
//! jsonschema::options()
//! .build(&alktype::BAST_META_SCHEMA)?
//! .validate(&bast_doc)?;
//! ```
use serde_json::{json, Value};
use std::sync::LazyLock;
/// The BAST v1 meta-schema, as a `serde_json::Value`.
///
/// A BAST document is valid against this schema iff it is well-formed
/// (correct `$defs` shape, known `kind` strings, required properties
/// present, no additional properties). Value-domain constraints
/// (`maxLength`, enum index bounds, etc.) are enforced by the
/// BAST-native validator, not this meta-schema.
///
/// Built lazily on first access via `serde_json::json!` (the `json!`
/// macro allocates, so it can't be a `const`). The parsed `Value` is
/// then reused for every subsequent call — `&'static` via `LazyLock`.
pub static BAST_META_SCHEMA: LazyLock<Value> = LazyLock::new(|| {
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://alk.dev/bast/v1/schema",
"title": "Binary Abstract Syntax Tree (BAST) v1",
"description": "Meta-schema for BAST documents. A BAST document describes the binary layout of structured data.",
"type": "object",
"properties": {
"$defs": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/TypeDef" }
}
},
"required": ["$defs"],
"$defs": {
"TypeDef": {
"oneOf": [
{ "$ref": "#/$defs/StructDef" },
{ "$ref": "#/$defs/UnionDef" },
{ "$ref": "#/$defs/EnumDef" }
]
},
"StructDef": {
"type": "object",
"properties": {
"kind": { "const": "struct" },
"endian": { "enum": ["little", "big"] },
"align": { "type": "integer", "minimum": 1 },
"fields": {
"type": "array",
"items": { "$ref": "#/$defs/FieldDef" }
}
},
"required": ["kind", "fields"],
"additionalProperties": false
},
"FieldDef": {
"type": "object",
"properties": {
"name": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
"kind": { "$ref": "#/$defs/TypeRef" },
"endian": { "enum": ["little", "big"] },
"align": { "type": "integer", "minimum": 1 },
"encoding": { "enum": ["length-prefixed", "offset-indirect"] },
"maxLength": { "type": "integer", "minimum": 0 }
},
"required": ["name", "kind"],
"additionalProperties": false
},
"TypeRef": {
"oneOf": [
{
"description": "Primitive type",
"type": "string",
"enum": [
"int8", "int16", "int32", "int64",
"uint8", "uint16", "uint32", "uint64",
"float32", "float64",
"bool", "string", "bytes", "timestamp"
]
},
{
"description": "Reference to a named $defs entry",
"type": "object",
"properties": {
"$ref": { "type": "string", "pattern": "^#/\\$defs/[a-zA-Z_][a-zA-Z0-9_]*$" }
},
"required": ["$ref"],
"additionalProperties": false
},
{
"description": "Array type (fixed-size only in v1 — count is required)",
"type": "object",
"properties": {
"kind": { "const": "array" },
"element": { "$ref": "#/$defs/TypeRef" },
"count": { "type": "integer", "minimum": 0 }
},
"required": ["kind", "element", "count"],
"additionalProperties": false
},
{
"description": "Record (string-keyed map) type",
"type": "object",
"properties": {
"kind": { "const": "record" },
"values": { "$ref": "#/$defs/TypeRef" }
},
"required": ["kind", "values"],
"additionalProperties": false
}
]
},
"UnionDef": {
"type": "object",
"properties": {
"kind": { "const": "union" },
"endian": { "enum": ["little", "big"] },
"fields": {
"type": "array",
"items": { "$ref": "#/$defs/FieldDef" }
},
"discriminator": {
"oneOf": [
{
"type": "object",
"properties": {
"kind": { "const": "byte" },
"offset": { "type": "integer", "minimum": 0 },
"type": { "enum": ["uint8", "uint16", "uint32"] }
},
"required": ["kind", "offset", "type"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "const": "field" },
"name": { "type": "string" }
},
"required": ["kind", "name"],
"additionalProperties": false
}
]
},
"mapping": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/TypeRef" }
}
},
"required": ["kind", "discriminator", "mapping"],
"additionalProperties": false
},
"EnumDef": {
"type": "object",
"properties": {
"kind": { "const": "enum" },
"values": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
},
"required": ["kind", "values"],
"additionalProperties": false
}
}
})
});
#[cfg(test)]
mod tests {
use super::BAST_META_SCHEMA;
#[test]
fn meta_schema_has_correct_id_and_draft() {
assert_eq!(
BAST_META_SCHEMA["$schema"],
"https://json-schema.org/draft/2020-12/schema"
);
assert_eq!(
BAST_META_SCHEMA["$id"],
"https://alk.dev/bast/v1/schema"
);
}
#[test]
fn meta_schema_requires_defs() {
let required = BAST_META_SCHEMA["required"].as_array().expect("required is array");
assert!(required.iter().any(|v| v == "$defs"));
}
#[test]
fn meta_schema_covers_struct_union_enum_type_defs() {
let type_def_oneof = BAST_META_SCHEMA["$defs"]["TypeDef"]["oneOf"]
.as_array()
.expect("TypeDef.oneOf is array");
let refs: Vec<&str> = type_def_oneof
.iter()
.map(|v| v["$ref"].as_str().expect("ref string"))
.collect();
assert!(refs.contains(&"#/$defs/StructDef"));
assert!(refs.contains(&"#/$defs/UnionDef"));
assert!(refs.contains(&"#/$defs/EnumDef"));
}
#[test]
fn meta_schema_typeref_enumerates_all_14_primitives() {
let typeref_oneof = BAST_META_SCHEMA["$defs"]["TypeRef"]["oneOf"]
.as_array()
.expect("TypeRef.oneOf is array");
let primitive_arm = typeref_oneof
.iter()
.find(|v| v["description"] == "Primitive type")
.expect("primitive arm present");
let primitives = primitive_arm["enum"]
.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);
for expected in [
"int8", "int16", "int32", "int64",
"uint8", "uint16", "uint32", "uint64",
"float32", "float64",
"bool", "string", "bytes", "timestamp",
] {
assert!(strs.contains(&expected), "missing primitive {expected}");
}
}
#[test]
fn meta_schema_validates_minimal_bast_document() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Point": {
"kind": "struct",
"fields": [
{ "name": "x", "kind": "uint32" },
{ "name": "y", "kind": "uint32" }
]
}
}
});
assert!(validator.validate(&doc).is_ok(), "valid doc rejected");
}
#[test]
fn meta_schema_rejects_missing_defs() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({ "type": "object" });
assert!(validator.validate(&doc).is_err(), "missing $defs accepted");
}
#[test]
fn meta_schema_rejects_unknown_kind() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Bad": {
"kind": "mystery",
"fields": []
}
}
});
assert!(validator.validate(&doc).is_err(), "unknown kind accepted");
}
#[test]
fn meta_schema_rejects_additional_properties_on_struct() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Bad": {
"kind": "struct",
"fields": [],
"bogus": true
}
}
});
assert!(validator.validate(&doc).is_err(), "additional property accepted");
}
#[test]
fn meta_schema_accepts_union_with_byte_discriminator() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Msg": {
"kind": "union",
"discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
"mapping": {
"1": { "$ref": "#/$defs/A" },
"2": { "$ref": "#/$defs/B" }
}
},
"A": { "kind": "struct", "fields": [] },
"B": { "kind": "struct", "fields": [] }
}
});
assert!(validator.validate(&doc).is_ok(), "byte-discriminator union rejected");
}
#[test]
fn meta_schema_accepts_enum() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Status": {
"kind": "enum",
"values": ["Ok", "Err"]
}
}
});
assert!(validator.validate(&doc).is_ok(), "enum rejected");
}
#[test]
fn meta_schema_rejects_empty_enum_values() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Bad": { "kind": "enum", "values": [] }
}
});
assert!(validator.validate(&doc).is_err(), "empty enum accepted");
}
#[test]
fn meta_schema_accepts_array_with_count() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Vec3": {
"kind": "struct",
"fields": [
{ "name": "data", "kind": { "kind": "array", "element": "float32", "count": 3 } }
]
}
}
});
assert!(validator.validate(&doc).is_ok(), "array with count rejected");
}
#[test]
fn meta_schema_rejects_array_without_count() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Bad": {
"kind": "struct",
"fields": [
{ "name": "data", "kind": { "kind": "array", "element": "float32" } }
]
}
}
});
assert!(validator.validate(&doc).is_err(), "array without count accepted");
}
#[test]
fn meta_schema_accepts_record() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Map": {
"kind": "struct",
"fields": [
{ "name": "entries", "kind": { "kind": "record", "values": "string" } }
]
}
}
});
assert!(validator.validate(&doc).is_ok(), "record rejected");
}
#[test]
fn meta_schema_accepts_ref_object() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Outer": {
"kind": "struct",
"fields": [
{ "name": "inner", "kind": { "$ref": "#/$defs/Inner" } }
]
},
"Inner": { "kind": "struct", "fields": [] }
}
});
assert!(validator.validate(&doc).is_ok(), "ref object rejected");
}
#[test]
fn meta_schema_rejects_malformed_ref() {
let validator = jsonschema::options()
.build(&BAST_META_SCHEMA)
.expect("meta-schema compiles");
let doc = serde_json::json!({
"$defs": {
"Bad": {
"kind": "struct",
"fields": [
{ "name": "inner", "kind": { "$ref": "Inner" } }
]
}
}
});
assert!(validator.validate(&doc).is_err(), "bare-name ref accepted");
}
}
+2
View File
@@ -28,6 +28,7 @@
#[macro_use]
mod macros;
pub mod bast_meta;
pub mod builder;
pub mod data_access;
pub mod engine;
@@ -40,6 +41,7 @@ pub mod sequential_reader;
pub mod tunion;
pub mod validation;
pub use bast_meta::BAST_META_SCHEMA;
pub use builder::{Definitions, Discriminator, Schema};
pub use engine::{LayoutMode, AlkTypeEngine};
pub use error::AlkTypeError;