Make BastDoc owned: drop Bast* lifetimes (ADR-012 §2a, plan phase 3)
Every Bast* type drops <'a>: &'a str -> String, &'a Value -> Value (deferred decision 1: plain String/Value — the tree is built once; Arc<str> name-sharing needs a bench justification that doesn't exist). BastDoc::new(&Value, &str) still takes references in and clones into owned storage; the doc gains Clone. resolve_ref/resolve_typeref/ resolve_typeref_as_def return owned types. - Engine ownership flip: AlkTypeEngine holds the owned BastDoc (replacing bast_doc: Value + root_name: String; root_name() delegates to the doc), killing its three per-call BastDoc::new re-parses (aligned validate_bytes, read_field, write_field — the review #004 M1 pattern removed by construction; phase 5 retires the lookup_leaf_field walk itself). New public accessor root_name() (additive). Engine Send + Sync with the owned doc, asserted in the existing thread-share test. - Bonus cleanup: materialize_typeref_packed's dead _field param dropped (phase 2 left it dangling). Under ownership, keeping it would force a deep Value clone per array element / record value / union variant via dummy_field_for. The param, dummy_field_for, and ty_source are gone; no behavior change (the arg was already ignored). BastField::synthetic keeps an owned-signature #[allow(dead_code)] definition (no remaining callers today). - Consumers adapted: OffsetMap::compute(&BastDoc), materialize_aligned(&BastDoc, ...) (no lifetime), BuildCtx/ ComputeCtx hold &'d BastDoc, tunion/discriminator name borrows, lib.rs module doc. LayoutBuilder's doc_value re-parse cache is unchanged pending phase 4. Verification: 465 tests pass with zero test-logic changes (bast.rs suites exercise every parser path through the public API); clippy -D warnings clean; cargo doc zero warnings; wasm32 release build green.
This commit is contained in:
1 parent
1641dab505
commit
c583762352
12 files changed
+299
-295
No files matched your search
+155
-145
@@ -11,11 +11,13 @@
|
||||
//! ## Design
|
||||
//!
|
||||
//! The parser produces a **typed tree** ([`BastDoc`]/[`BastDef`]/[`BastType`]/…)
|
||||
//! that borrows from the source [`serde_json::Value`]. Three consumers
|
||||
//! (layout, materialize, validate) walk the same tree, so a typed view
|
||||
//! pays for itself: each walks matched arms over [`BastType`] instead of
|
||||
//! re-parsing raw JSON at every node. Borrowing (not cloning) keeps the
|
||||
//! parse allocation-free beyond the small typed nodes themselves.
|
||||
//! that owns its data (ADR-012 §2a). Three consumers (layout, materialize,
|
||||
//! validate) walk the same tree, so a typed view pays for itself: each
|
||||
//! walks matched arms over [`BastType`] instead of re-parsing raw JSON at
|
||||
//! every node. Ownership (not borrowing) lets [`BastDoc`] be stored on
|
||||
//! its consumers (`AlkTypeEngine`, `LayoutBuilder`) without lifetime
|
||||
//! entanglement — the clone happens once at [`BastDoc::new`], not per
|
||||
//! field.
|
||||
//!
|
||||
//! `$ref` resolution is a single hash lookup against `$defs` — BAST refs
|
||||
//! are always full JSON Pointers restricted to `#/$defs/<name>`. Union
|
||||
@@ -45,16 +47,18 @@ const REF_PREFIX: &str = "#/$defs/";
|
||||
///
|
||||
/// Built once via [`BastDoc::new`] from the raw BAST `Value`; the
|
||||
/// downstream `compile()` call (step 4) will construct this and hand it
|
||||
/// to the layout engines. The tree borrows from `root` — keep the
|
||||
/// source `Value` alive for the lifetime of the doc.
|
||||
#[derive(Debug)]
|
||||
pub struct BastDoc<'a> {
|
||||
root: &'a Value,
|
||||
root_name: &'a str,
|
||||
root_def: BastDef<'a>,
|
||||
/// to the layout engines. The tree is **owned** (ADR-012 §2a): `new`
|
||||
/// clones the root `Value` and the root name into the doc, so the doc
|
||||
/// can be stored on consumers without borrowing from the caller's
|
||||
/// input.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastDoc {
|
||||
root: Value,
|
||||
root_name: String,
|
||||
root_def: BastDef,
|
||||
}
|
||||
|
||||
impl<'a> BastDoc<'a> {
|
||||
impl BastDoc {
|
||||
/// Parse a BAST document, selecting `root_name` as the top-level type.
|
||||
///
|
||||
/// Validates the document structurally as it parses: the `$defs`
|
||||
@@ -64,26 +68,27 @@ impl<'a> BastDoc<'a> {
|
||||
/// root definition and the definitions it (transitively) references
|
||||
/// are parsed eagerly; orphan `$defs` entries are not checked.
|
||||
///
|
||||
/// The raw `Value` is also retained so lazy `$ref` resolution
|
||||
/// ([`BastDoc::resolve_typeref`]) can reach any `$defs` entry at access time.
|
||||
pub fn new(root: &'a Value, root_name: &'a str) -> Result<Self, AlkTypeError> {
|
||||
/// The raw `Value` is cloned into the doc so lazy `$ref` resolution
|
||||
/// ([`BastDoc::resolve_typeref`]) can reach any `$defs` entry at
|
||||
/// access time.
|
||||
pub fn new(root: &Value, root_name: &str) -> Result<Self, AlkTypeError> {
|
||||
let raw_root_def = Self::lookup_def_raw(root, root_name)?;
|
||||
let root_def = BastDef::parse(raw_root_def, root_name, "")?;
|
||||
let doc = Self {
|
||||
root,
|
||||
root_name,
|
||||
root: root.clone(),
|
||||
root_name: root_name.to_string(),
|
||||
root_def,
|
||||
};
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// The root type name this doc was compiled with.
|
||||
pub fn root_name(&self) -> &'a str {
|
||||
self.root_name
|
||||
pub fn root_name(&self) -> &str {
|
||||
&self.root_name
|
||||
}
|
||||
|
||||
/// The parsed root type definition.
|
||||
pub fn root_def(&self) -> &BastDef<'a> {
|
||||
pub fn root_def(&self) -> &BastDef {
|
||||
&self.root_def
|
||||
}
|
||||
|
||||
@@ -92,8 +97,8 @@ impl<'a> BastDoc<'a> {
|
||||
/// The single hash lookup that replaces v0.1.0's ref-normalization
|
||||
/// machinery. BAST refs are always `#/$defs/<name>`;
|
||||
/// no external refs, no fragment-only pointers, no bare names.
|
||||
pub fn lookup_def(&self, name: &str) -> Result<&'a Value, AlkTypeError> {
|
||||
Self::lookup_def_raw(self.root, name)
|
||||
pub fn lookup_def(&self, name: &str) -> Result<&Value, AlkTypeError> {
|
||||
Self::lookup_def_raw(&self.root, name)
|
||||
}
|
||||
|
||||
/// Resolve a [`BastType::Ref`] to the [`BastDef`] it names.
|
||||
@@ -102,7 +107,7 @@ impl<'a> BastDoc<'a> {
|
||||
/// for union variant dispatch and `$ref` fields. The parser does not
|
||||
/// inline refs at construction time — variant refs stay as
|
||||
/// [`BastRef`]s and resolve on demand.
|
||||
pub fn resolve_ref(&self, r: &BastRef<'a>) -> Result<BastDef<'a>, AlkTypeError> {
|
||||
pub fn resolve_ref(&self, r: &BastRef) -> Result<BastDef, AlkTypeError> {
|
||||
let raw = self.lookup_def(r.name())?;
|
||||
BastDef::parse(raw, r.name(), "")
|
||||
}
|
||||
@@ -114,7 +119,7 @@ impl<'a> BastDoc<'a> {
|
||||
/// The composite-walkers (struct fields, array elements, record
|
||||
/// values, union mappings) call this to deref one level. Deeper
|
||||
/// `$ref` chains are resolved by recursing through the consumer.
|
||||
pub fn resolve_typeref(&self, ty: &BastType<'a>) -> Result<BastType<'a>, AlkTypeError> {
|
||||
pub fn resolve_typeref(&self, ty: &BastType) -> Result<BastType, AlkTypeError> {
|
||||
match ty {
|
||||
BastType::Ref(r) => {
|
||||
let def = self.resolve_ref(r)?;
|
||||
@@ -134,25 +139,25 @@ impl<'a> BastDoc<'a> {
|
||||
/// uniformly.
|
||||
pub fn resolve_typeref_as_def(
|
||||
&self,
|
||||
ty: &BastType<'a>,
|
||||
ty: &BastType,
|
||||
owning_path: &str,
|
||||
) -> Result<BastDef<'a>, AlkTypeError> {
|
||||
) -> Result<BastDef, AlkTypeError> {
|
||||
match ty {
|
||||
BastType::Ref(r) => self.resolve_ref(r),
|
||||
BastType::Struct(s) => Ok(BastDef {
|
||||
name: "",
|
||||
name: String::new(),
|
||||
kind: BastDefKind::Struct(s.clone()),
|
||||
source: s.source,
|
||||
source: s.source.clone(),
|
||||
}),
|
||||
BastType::Union(u) => Ok(BastDef {
|
||||
name: "",
|
||||
name: String::new(),
|
||||
kind: BastDefKind::Union(u.clone()),
|
||||
source: u.source,
|
||||
source: u.source.clone(),
|
||||
}),
|
||||
BastType::Enum(e) => Ok(BastDef {
|
||||
name: "",
|
||||
name: String::new(),
|
||||
kind: BastDefKind::Enum(e.clone()),
|
||||
source: e.source,
|
||||
source: e.source.clone(),
|
||||
}),
|
||||
BastType::Primitive(_) | BastType::Array(_) | BastType::Record(_) => {
|
||||
Err(AlkTypeError::Schema(format!(
|
||||
@@ -162,7 +167,7 @@ impl<'a> BastDoc<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_def_raw(root: &'a Value, name: &str) -> Result<&'a Value, AlkTypeError> {
|
||||
fn lookup_def_raw<'d>(root: &'d Value, name: &str) -> Result<&'d Value, AlkTypeError> {
|
||||
let defs = root
|
||||
.get(DEFS_KEY)
|
||||
.and_then(Value::as_object)
|
||||
@@ -180,33 +185,33 @@ impl<'a> BastDoc<'a> {
|
||||
/// arrays, and records only appear as field/element/value types
|
||||
/// ([`BastType`]), not as named definitions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastDef<'a> {
|
||||
name: &'a str,
|
||||
kind: BastDefKind<'a>,
|
||||
source: &'a Value,
|
||||
pub struct BastDef {
|
||||
name: String,
|
||||
kind: BastDefKind,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastDef<'a> {
|
||||
impl BastDef {
|
||||
/// The `$defs` key this definition was registered under. Empty
|
||||
/// string for synthetic definitions produced by
|
||||
/// [`BastDoc::resolve_typeref_as_def`] from an inline type.
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// The typed kind — struct, union, or enum.
|
||||
pub fn kind(&self) -> &BastDefKind<'a> {
|
||||
pub fn kind(&self) -> &BastDefKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
/// The raw JSON node this definition was parsed from. Retained for
|
||||
/// consumers that need properties the typed view doesn't expose yet
|
||||
/// (e.g. the layout engines during the step 4 migration).
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, name: &'a str, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, name: &str, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let kind_str = node
|
||||
.get("kind")
|
||||
.and_then(Value::as_str)
|
||||
@@ -233,25 +238,25 @@ impl<'a> BastDef<'a> {
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
name,
|
||||
name: name.to_string(),
|
||||
kind,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The typed kind of a [`BastDef`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BastDefKind<'a> {
|
||||
pub enum BastDefKind {
|
||||
/// `kind: "struct"` — an ordered list of fields.
|
||||
Struct(BastStruct<'a>),
|
||||
Struct(BastStruct),
|
||||
/// `kind: "union"` — discriminator + mapping of variant TypeRefs.
|
||||
Union(BastUnion<'a>),
|
||||
Union(BastUnion),
|
||||
/// `kind: "enum"` — a non-empty list of string values.
|
||||
Enum(BastEnum<'a>),
|
||||
Enum(BastEnum),
|
||||
}
|
||||
|
||||
impl<'a> BastDefKind<'a> {
|
||||
impl BastDefKind {
|
||||
/// The [`AlkTypeKind`] of this definition.
|
||||
pub fn alk_kind(&self) -> AlkTypeKind {
|
||||
match self {
|
||||
@@ -269,14 +274,14 @@ impl<'a> BastDefKind<'a> {
|
||||
/// ([bast-format.md §Design Principles](../docs/architecture/bast-format.md#design-principles)
|
||||
/// #4).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastStruct<'a> {
|
||||
pub struct BastStruct {
|
||||
endian: Endian,
|
||||
align: Option<usize>,
|
||||
fields: Vec<BastField<'a>>,
|
||||
source: &'a Value,
|
||||
fields: Vec<BastField>,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastStruct<'a> {
|
||||
impl BastStruct {
|
||||
/// The struct-level default endianness. Defaults to
|
||||
/// [`Endian::Little`] when absent (ADR-003).
|
||||
pub fn endian(&self) -> Endian {
|
||||
@@ -290,16 +295,16 @@ impl<'a> BastStruct<'a> {
|
||||
}
|
||||
|
||||
/// The ordered field list. Array position is field order.
|
||||
pub fn fields(&self) -> &[BastField<'a>] {
|
||||
pub fn fields(&self) -> &[BastField] {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
/// The raw JSON node this struct was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let endian = parse_endian_opt(node).unwrap_or(Endian::Little);
|
||||
let align = parse_align(node);
|
||||
let raw_fields = node
|
||||
@@ -319,32 +324,33 @@ impl<'a> BastStruct<'a> {
|
||||
endian,
|
||||
align,
|
||||
fields,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A field within a struct or field-name-discriminator union.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastField<'a> {
|
||||
name: &'a str,
|
||||
ty: BastType<'a>,
|
||||
pub struct BastField {
|
||||
name: String,
|
||||
ty: BastType,
|
||||
endian: Option<Endian>,
|
||||
align: Option<usize>,
|
||||
encoding: VariableEncoding,
|
||||
max_length: Option<usize>,
|
||||
source: &'a Value,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastField<'a> {
|
||||
impl BastField {
|
||||
/// Construct a synthetic field wrapping `ty` with no annotations.
|
||||
/// Used internally by the materializer for array elements, record
|
||||
/// values, and union variants — these are TypeRefs, not fields, so
|
||||
/// they don't carry field-level annotations. `source` is the
|
||||
/// `BastType`'s source node (or `&Value::Null` for primitives).
|
||||
pub(crate) fn synthetic(ty: BastType<'a>, source: &'a Value) -> Self {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn synthetic(ty: BastType, source: Value) -> Self {
|
||||
Self {
|
||||
name: "",
|
||||
name: String::new(),
|
||||
ty,
|
||||
endian: None,
|
||||
align: None,
|
||||
@@ -356,12 +362,12 @@ impl<'a> BastField<'a> {
|
||||
|
||||
/// The field name. Guaranteed to match `^[a-zA-Z_][a-zA-Z0-9_]*$` by
|
||||
/// the meta-schema; the parser does not re-check the pattern.
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// The field's type — a primitive, `$ref`, array, or record.
|
||||
pub fn ty(&self) -> &BastType<'a> {
|
||||
pub fn ty(&self) -> &BastType {
|
||||
&self.ty
|
||||
}
|
||||
|
||||
@@ -391,8 +397,8 @@ impl<'a> BastField<'a> {
|
||||
}
|
||||
|
||||
/// The raw JSON node this field was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
/// The effective endianness: this field's override, or the container
|
||||
@@ -401,7 +407,7 @@ impl<'a> BastField<'a> {
|
||||
self.endian.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let name = node
|
||||
.get("name")
|
||||
.and_then(Value::as_str)
|
||||
@@ -423,13 +429,13 @@ impl<'a> BastField<'a> {
|
||||
let encoding = parse_encoding(node);
|
||||
let max_length = parse_max_length(node);
|
||||
Ok(Self {
|
||||
name,
|
||||
name: name.to_string(),
|
||||
ty,
|
||||
endian,
|
||||
align,
|
||||
encoding,
|
||||
max_length,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -440,52 +446,52 @@ impl<'a> BastField<'a> {
|
||||
/// Variant `$ref`s are resolved **lazily** by the materializer/validator
|
||||
/// via [`BastDoc::resolve_typeref`] — no compile-time inlining.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastUnion<'a> {
|
||||
pub struct BastUnion {
|
||||
endian: Endian,
|
||||
discriminator: BastDiscriminator<'a>,
|
||||
fields: Vec<BastField<'a>>,
|
||||
mapping: Vec<(&'a str, BastType<'a>)>,
|
||||
source: &'a Value,
|
||||
discriminator: BastDiscriminator,
|
||||
fields: Vec<BastField>,
|
||||
mapping: Vec<(String, BastType)>,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastUnion<'a> {
|
||||
impl BastUnion {
|
||||
/// The union-level default endianness for variant fields.
|
||||
pub fn endian(&self) -> Endian {
|
||||
self.endian
|
||||
}
|
||||
|
||||
/// The discriminator — byte-offset or field-name.
|
||||
pub fn discriminator(&self) -> &BastDiscriminator<'a> {
|
||||
pub fn discriminator(&self) -> &BastDiscriminator {
|
||||
&self.discriminator
|
||||
}
|
||||
|
||||
/// The optional `fields` array. Only valid with field-name
|
||||
/// discriminators (D-BAST-005); empty for byte-offset discriminators.
|
||||
pub fn fields(&self) -> &[BastField<'a>] {
|
||||
pub fn fields(&self) -> &[BastField] {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
/// The `mapping` entries, in document order. Each key is a
|
||||
/// stringified discriminator value; each value is the variant
|
||||
/// [`BastType`] (typically a `$ref`).
|
||||
pub fn mapping(&self) -> &[(&'a str, BastType<'a>)] {
|
||||
pub fn mapping(&self) -> &[(String, BastType)] {
|
||||
&self.mapping
|
||||
}
|
||||
|
||||
/// Look up the variant [`BastType`] for a discriminator value.
|
||||
pub fn variant_for(&self, key: &str) -> Option<&BastType<'a>> {
|
||||
pub fn variant_for(&self, key: &str) -> Option<&BastType> {
|
||||
self.mapping
|
||||
.iter()
|
||||
.find(|(k, _)| *k == key)
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// The raw JSON node this union was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let endian = parse_endian_opt(node).unwrap_or(Endian::Little);
|
||||
let discriminator = BastDiscriminator::parse(node, path)?;
|
||||
let raw_fields = node.get("fields").and_then(Value::as_array);
|
||||
@@ -524,7 +530,7 @@ impl<'a> BastUnion<'a> {
|
||||
for (key, value) in raw_mapping.iter() {
|
||||
let entry_path = format!("{path}.mapping[{key}]");
|
||||
let ty = BastType::parse(value, &entry_path)?;
|
||||
mapping.push((key.as_str(), ty));
|
||||
mapping.push((key.clone(), ty));
|
||||
}
|
||||
if mapping.is_empty() {
|
||||
return Err(AlkTypeError::Schema(format!(
|
||||
@@ -536,14 +542,14 @@ impl<'a> BastUnion<'a> {
|
||||
discriminator,
|
||||
fields,
|
||||
mapping,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A union discriminator — byte-offset or field-name (ADR-003, D-BAST-005).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BastDiscriminator<'a> {
|
||||
pub enum BastDiscriminator {
|
||||
/// A fixed-size integer at a known byte offset. Mapping keys are
|
||||
/// stringified integers.
|
||||
Byte {
|
||||
@@ -557,12 +563,12 @@ pub enum BastDiscriminator<'a> {
|
||||
/// declares the discriminator field alongside any shared fields.
|
||||
Field {
|
||||
/// The field name that holds the discriminator value.
|
||||
name: &'a str,
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> BastDiscriminator<'a> {
|
||||
fn parse(union_node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
impl BastDiscriminator {
|
||||
fn parse(union_node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let disc = union_node.get("discriminator").ok_or_else(|| {
|
||||
AlkTypeError::Schema(format!(
|
||||
"bast: union at {path} has no `discriminator`"
|
||||
@@ -613,7 +619,9 @@ impl<'a> BastDiscriminator<'a> {
|
||||
"bast: field discriminator at {path} has no `name`"
|
||||
))
|
||||
})?;
|
||||
Ok(BastDiscriminator::Field { name })
|
||||
Ok(BastDiscriminator::Field {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
other => Err(AlkTypeError::Schema(format!(
|
||||
"bast: unknown discriminator kind {other:?} at {path}"
|
||||
@@ -631,24 +639,24 @@ impl<'a> BastDiscriminator<'a> {
|
||||
/// built-in `enum` keyword checked string membership but the materializer
|
||||
/// emitted a numeric index that never matched.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastEnum<'a> {
|
||||
values: Vec<&'a str>,
|
||||
source: &'a Value,
|
||||
pub struct BastEnum {
|
||||
values: Vec<String>,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastEnum<'a> {
|
||||
impl BastEnum {
|
||||
/// The declared values, in declaration order. Non-empty (enforced
|
||||
/// by the meta-schema's `minItems: 1` and re-checked here).
|
||||
pub fn values(&self) -> &[&'a str] {
|
||||
pub fn values(&self) -> &[String] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
/// The raw JSON node this enum was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let raw_values = node
|
||||
.get("values")
|
||||
.and_then(Value::as_array)
|
||||
@@ -669,11 +677,11 @@ impl<'a> BastEnum<'a> {
|
||||
"bast: enum at {path} values[{i}] is not a string"
|
||||
))
|
||||
})?;
|
||||
values.push(s);
|
||||
values.push(s.to_string());
|
||||
}
|
||||
Ok(Self {
|
||||
values,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -688,25 +696,25 @@ impl<'a> BastEnum<'a> {
|
||||
/// `enum`) appear inline here when a field/element/value is an anonymous
|
||||
/// composite; named composites go through [`BastType::Ref`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BastType<'a> {
|
||||
pub enum BastType {
|
||||
/// A built-in primitive (`"uint32"`, `"string"`, etc.).
|
||||
Primitive(AlkTypeKind),
|
||||
/// A `$ref` to a named `$defs` entry: `{ "$ref": "#/$defs/Name" }`.
|
||||
Ref(BastRef<'a>),
|
||||
Ref(BastRef),
|
||||
/// `kind: "array"` — a fixed-size array (count required in v1,
|
||||
/// D-BAST-004).
|
||||
Array(BastArray<'a>),
|
||||
Array(BastArray),
|
||||
/// `kind: "record"` — a string-keyed map.
|
||||
Record(BastRecord<'a>),
|
||||
Record(BastRecord),
|
||||
/// An inline `kind: "struct"`.
|
||||
Struct(BastStruct<'a>),
|
||||
Struct(BastStruct),
|
||||
/// An inline `kind: "union"`.
|
||||
Union(BastUnion<'a>),
|
||||
Union(BastUnion),
|
||||
/// An inline `kind: "enum"`.
|
||||
Enum(BastEnum<'a>),
|
||||
Enum(BastEnum),
|
||||
}
|
||||
|
||||
impl<'a> BastType<'a> {
|
||||
impl BastType {
|
||||
/// The [`AlkTypeKind`] of this type. For [`Ref`](Self::Ref), this is
|
||||
/// [`AlkTypeKind::Struct`] — the meta-schema only allows
|
||||
/// struct/union/enum as named definitions, so a `$ref` always lands
|
||||
@@ -724,7 +732,7 @@ impl<'a> BastType<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
if let Some(s) = node.as_str() {
|
||||
let k = AlkTypeKind::from_bast_str(s)?;
|
||||
return Ok(BastType::Primitive(k));
|
||||
@@ -759,7 +767,7 @@ impl<'a> BastType<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_def(def: BastDef<'a>) -> Self {
|
||||
fn from_def(def: BastDef) -> Self {
|
||||
match def.kind {
|
||||
BastDefKind::Struct(s) => BastType::Struct(s),
|
||||
BastDefKind::Union(u) => BastType::Union(u),
|
||||
@@ -774,18 +782,18 @@ impl<'a> BastType<'a> {
|
||||
/// ([`BastDoc::lookup_def`]); the v0.1.0 engine needed a normalization
|
||||
/// pass for TypeBox's bare-name refs, but BAST forbids them.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastRef<'a> {
|
||||
name: &'a str,
|
||||
pub struct BastRef {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl<'a> BastRef<'a> {
|
||||
impl BastRef {
|
||||
/// The referenced `$defs` entry name (e.g. `Read` for
|
||||
/// `#/$defs/Read`).
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn parse(ref_path: &'a str, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(ref_path: &str, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let name = ref_path.strip_prefix(REF_PREFIX).ok_or_else(|| {
|
||||
AlkTypeError::Schema(format!(
|
||||
"bast: $ref at {path} is {ref_path:?}, expected \"#/$defs/<name>\""
|
||||
@@ -796,7 +804,9 @@ impl<'a> BastRef<'a> {
|
||||
"bast: $ref at {path} has an empty name after \"#/$defs/\""
|
||||
)));
|
||||
}
|
||||
Ok(Self { name })
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,15 +817,15 @@ impl<'a> BastRef<'a> {
|
||||
/// element type is itself a [`BastType`], so nested `$ref`s work:
|
||||
/// `{ "kind": "array", "element": { "$ref": "#/$defs/Point" }, "count": 4 }`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastArray<'a> {
|
||||
element: Box<BastType<'a>>,
|
||||
pub struct BastArray {
|
||||
element: Box<BastType>,
|
||||
count: usize,
|
||||
source: &'a Value,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastArray<'a> {
|
||||
impl BastArray {
|
||||
/// The element type.
|
||||
pub fn element(&self) -> &BastType<'a> {
|
||||
pub fn element(&self) -> &BastType {
|
||||
&self.element
|
||||
}
|
||||
|
||||
@@ -825,11 +835,11 @@ impl<'a> BastArray<'a> {
|
||||
}
|
||||
|
||||
/// The raw JSON node this array type was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let element_node = node.get("element").ok_or_else(|| {
|
||||
AlkTypeError::Schema(format!(
|
||||
"bast: array at {path} has no `element`"
|
||||
@@ -844,7 +854,7 @@ impl<'a> BastArray<'a> {
|
||||
Ok(Self {
|
||||
element: Box::new(element),
|
||||
count,
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -855,23 +865,23 @@ impl<'a> BastArray<'a> {
|
||||
/// `[count: u32][key_len: u32][key_bytes][value]...` repeated `count`
|
||||
/// times (per [bast-format.md §Record](../docs/architecture/bast-format.md#record)).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BastRecord<'a> {
|
||||
values: Box<BastType<'a>>,
|
||||
source: &'a Value,
|
||||
pub struct BastRecord {
|
||||
values: Box<BastType>,
|
||||
source: Value,
|
||||
}
|
||||
|
||||
impl<'a> BastRecord<'a> {
|
||||
impl BastRecord {
|
||||
/// The value type.
|
||||
pub fn values(&self) -> &BastType<'a> {
|
||||
pub fn values(&self) -> &BastType {
|
||||
&self.values
|
||||
}
|
||||
|
||||
/// The raw JSON node this record type was parsed from.
|
||||
pub fn source(&self) -> &'a Value {
|
||||
self.source
|
||||
pub fn source(&self) -> &Value {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn parse(node: &'a Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
fn parse(node: &Value, path: &str) -> Result<Self, AlkTypeError> {
|
||||
let values_node = node.get("values").ok_or_else(|| {
|
||||
AlkTypeError::Schema(format!(
|
||||
"bast: record at {path} has no `values`"
|
||||
@@ -880,7 +890,7 @@ impl<'a> BastRecord<'a> {
|
||||
let values = BastType::parse(values_node, &format!("{path}.values"))?;
|
||||
Ok(Self {
|
||||
values: Box::new(values),
|
||||
source: node,
|
||||
source: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -954,7 +964,7 @@ fn parse_usize_field(
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for BastType<'a> {
|
||||
impl std::fmt::Display for BastType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BastType::Primitive(k) => write!(f, "{}", k.to_bast_str()),
|
||||
@@ -973,7 +983,7 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn doc_from<'a>(root: &'a Value, root_name: &'a str) -> BastDoc<'a> {
|
||||
fn doc_from(root: &Value, root_name: &str) -> BastDoc {
|
||||
BastDoc::new(root, root_name).expect("bast doc")
|
||||
}
|
||||
|
||||
@@ -1719,7 +1729,7 @@ mod tests {
|
||||
let arr = BastType::Array(BastArray {
|
||||
element: Box::new(BastType::Primitive(AlkTypeKind::Float32)),
|
||||
count: 3,
|
||||
source: &Value::Null,
|
||||
source: Value::Null,
|
||||
});
|
||||
assert_eq!(format!("{arr}"), "array<float32>[3]");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user