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:
glm-5.3-flash committed 2026-09-02 08:31:58 +00:00
1 parent 1641dab505
commit c583762352
12 files changed
+299 -295

No files matched your search

+155 -145
View File
@@ -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]");
}