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
@@ -379,7 +379,35 @@ message).
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Owned `BastDoc` (ADR-012 §2a)
|
||||
## Phase 3 — Owned `BastDoc` (ADR-012 §2a) — **DONE (2026-09-02)**
|
||||
|
||||
> **Status: implemented.** Every `Bast*` type dropped its `<'a>`:
|
||||
> `&'a str` → `String`, `&'a Value` → `Value` (deferred decision 1
|
||||
> resolved: plain `String`/`Value` — the tree is built once, name
|
||||
> sharing via `Arc<str>` needs a bench justification that doesn't
|
||||
> exist). `BastDoc::new(&Value, &str)` still takes references in and
|
||||
> clones into owned storage; `BastDoc` gained `Clone`. `resolve_*`
|
||||
> return owned `BastDef`/`BastType`. Consumers adapted:
|
||||
> `OffsetMap::compute(&BastDoc)`, `materialize_aligned(&BastDoc, ...)`
|
||||
> (no lifetime), `BuildCtx`/`ComputeCtx` hold `&'d BastDoc`.
|
||||
> **Engine ownership flip:** `AlkTypeEngine` now 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 (`validate_bytes` aligned path,
|
||||
> `read_field`, `write_field` — review #004 M1 sites by construction;
|
||||
> phase 5 removes the `lookup_leaf_field` walk itself). New public
|
||||
> accessor `AlkTypeEngine::root_name()` (additive). **Bonus cleanup:**
|
||||
> `materialize_typeref_packed`'s dead `_field` param dropped (phase 2
|
||||
> left it dangling); under ownership it would have forced 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 `_field` arg was already
|
||||
> ignored). `BastField::synthetic` retains an owned-signature
|
||||
> `#[allow(dead_code)]` definition (no remaining callers; kept for the
|
||||
> phase-4/5-aligned materializer helpers if they need it). Existing
|
||||
> `BastDoc` consumers (`LayoutBuilder`'s `doc_value` re-parse cache)
|
||||
> are unchanged pending phase 4. Engine stays `Send + Sync` with the
|
||||
> owned doc — the engine's thread-share test now asserts it directly.
|
||||
|
||||
**Goal:** Make `BastDoc` own its data (drop the `<'a>` lifetime).
|
||||
`&'a str` → `String`, `&'a Value` → `Value` (or `Arc<str>`/`Arc<Value>`
|
||||
|
||||
+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]");
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ pub(crate) const DISCRIMINATOR_KEY: &str = "__discriminator";
|
||||
/// constraint, or `Err(AlkTypeError::Schema(...))` if the BAST document
|
||||
/// is malformed (a dangling `$ref`, a cyclic `$ref`, a missing `values`
|
||||
/// array, etc.).
|
||||
pub fn validate_value(doc: &BastDoc<'_>, value: &Value) -> Result<(), AlkTypeError> {
|
||||
pub fn validate_value(doc: &BastDoc, value: &Value) -> Result<(), AlkTypeError> {
|
||||
let plan = crate::validation_plan::ValidationPlan::compile(doc)?;
|
||||
plan.validate(value)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ mod tests {
|
||||
use crate::read_plan::ReadPlan;
|
||||
use serde_json::json;
|
||||
|
||||
fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> {
|
||||
fn doc_from(root: &Value, name: &str) -> BastDoc {
|
||||
BastDoc::new(root, name).expect("bast doc")
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ mod tests {
|
||||
|
||||
fn materialize_and_validate(
|
||||
root: &Value,
|
||||
doc: &BastDoc<'_>,
|
||||
doc: &BastDoc,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), AlkTypeError> {
|
||||
let plan = ReadPlan::compile(root, doc.root_name())?;
|
||||
|
||||
+22
-17
@@ -87,8 +87,7 @@ pub struct AlkTypeEngine {
|
||||
json_validator: Option<jsonschema::Validator>,
|
||||
validation_plan: Arc<ValidationPlan>,
|
||||
endian: Endian,
|
||||
bast_doc: Value,
|
||||
root_name: String,
|
||||
doc: BastDoc,
|
||||
}
|
||||
|
||||
impl AlkTypeEngine {
|
||||
@@ -114,10 +113,10 @@ impl AlkTypeEngine {
|
||||
/// BAST document — BAST describes bytes, not JSON shape. It may be
|
||||
/// authored separately or derived from BAST via future codegen.
|
||||
///
|
||||
/// The engine retains a clone of the BAST `Value` so that
|
||||
/// [`AlkTypeEngine::sequential_reader`] and
|
||||
/// [`AlkTypeEngine::read_field`] can re-parse the typed tree on
|
||||
/// demand without lifetime entanglement with the caller's `Value`.
|
||||
/// The engine retains an owned [`BastDoc`] (ADR-012 §2a) so that
|
||||
/// [`AlkTypeEngine::validate_bytes`] and
|
||||
/// [`AlkTypeEngine::read_field`] can walk the typed tree without
|
||||
/// re-parsing the raw document per call.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -171,8 +170,7 @@ impl AlkTypeEngine {
|
||||
json_validator,
|
||||
validation_plan,
|
||||
endian,
|
||||
bast_doc: bast_doc.clone(),
|
||||
root_name: root_name.to_string(),
|
||||
doc,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,6 +179,11 @@ impl AlkTypeEngine {
|
||||
self.endian
|
||||
}
|
||||
|
||||
/// The root type name this engine was compiled with (D-BAST-001).
|
||||
pub fn root_name(&self) -> &str {
|
||||
self.doc.root_name()
|
||||
}
|
||||
|
||||
/// The layout mode this engine was compiled with.
|
||||
pub fn mode(&self) -> LayoutMode {
|
||||
match self.layout {
|
||||
@@ -302,13 +305,12 @@ impl AlkTypeEngine {
|
||||
/// (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 {
|
||||
Layout::Packed { plan, .. } => {
|
||||
materialize::materialize_packed(plan, buffer)?
|
||||
}
|
||||
Layout::Aligned { offset_map } => {
|
||||
materialize::materialize_aligned(&doc, buffer, offset_map)?
|
||||
materialize::materialize_aligned(&self.doc, buffer, offset_map)?
|
||||
}
|
||||
};
|
||||
self.validation_plan.validate(&value)
|
||||
@@ -369,8 +371,7 @@ impl AlkTypeEngine {
|
||||
field_path: field_path.to_string(),
|
||||
reason: "field not found in offset map".to_string(),
|
||||
})?;
|
||||
let doc = BastDoc::new(&self.bast_doc, &self.root_name)?;
|
||||
let leaf = lookup_leaf_field(&doc, field_path).ok_or_else(|| AlkTypeError::Offset {
|
||||
let leaf = lookup_leaf_field(&self.doc, field_path).ok_or_else(|| AlkTypeError::Offset {
|
||||
field_path: field_path.to_string(),
|
||||
reason: "field schema not found in BAST tree or has no primitive kind".to_string(),
|
||||
})?;
|
||||
@@ -502,8 +503,7 @@ impl AlkTypeEngine {
|
||||
field_path: field_path.to_string(),
|
||||
reason: "field not found in offset map".to_string(),
|
||||
})?;
|
||||
let doc = BastDoc::new(&self.bast_doc, &self.root_name)?;
|
||||
let leaf = lookup_leaf_field(&doc, field_path).ok_or_else(|| AlkTypeError::Offset {
|
||||
let leaf = lookup_leaf_field(&self.doc, field_path).ok_or_else(|| AlkTypeError::Offset {
|
||||
field_path: field_path.to_string(),
|
||||
reason: "field schema not found in BAST tree or has no primitive kind".to_string(),
|
||||
})?;
|
||||
@@ -583,7 +583,7 @@ impl fmt::Debug for AlkTypeEngine {
|
||||
.field("layout", &self.layout)
|
||||
.field("json_validator", &self.json_validator.as_ref().map(|_| "<jsonschema::Validator>"))
|
||||
.field("endian", &self.endian)
|
||||
.field("root_name", &self.root_name)
|
||||
.field("root_name", &self.root_name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -605,9 +605,9 @@ struct LeafFieldInfo {
|
||||
/// `fields` at each step, resolving `$ref`s via [`BastDoc::resolve_typeref`].
|
||||
/// Array element segments (`field[i]`) are not handled here —
|
||||
/// `read_field`/`write_field` only address leaf fields.
|
||||
fn lookup_leaf_field(doc: &BastDoc<'_>, field_path: &str) -> Option<LeafFieldInfo> {
|
||||
fn lookup_leaf_field(doc: &BastDoc, field_path: &str) -> Option<LeafFieldInfo> {
|
||||
let root_def = doc.root_def();
|
||||
let mut current_struct: BastStruct<'_> = match root_def.kind() {
|
||||
let mut current_struct: BastStruct = match root_def.kind() {
|
||||
BastDefKind::Struct(s) => s.clone(),
|
||||
_ => return None,
|
||||
};
|
||||
@@ -1399,6 +1399,11 @@ mod tests {
|
||||
AlkTypeEngine::compile(&doc, "S", LayoutMode::Packed, None).expect("compile");
|
||||
fn assert_send_sync<T: Send + Sync>(_: &T) {}
|
||||
assert_send_sync(engine.validation_plan());
|
||||
// The engine itself must stay Send + Sync now that it holds the
|
||||
// owned BastDoc (ADR-012 §2a) — alkcall shares engines across
|
||||
// hub/spoke threads.
|
||||
fn assert_engine_send_sync<T: Send + Sync>(_: &T) {}
|
||||
assert_engine_send_sync(&engine);
|
||||
let shared:std::sync::Arc<_> = engine.validation_plan().clone();
|
||||
let handle = std::thread::spawn(move || shared.validate(&json!({"id": 1})).is_ok());
|
||||
assert!(handle.join().expect("join"));
|
||||
|
||||
@@ -214,7 +214,7 @@ impl LayoutBuilder {
|
||||
|
||||
/// Mutable context threaded through the recursive layout computation.
|
||||
struct BuildCtx<'d> {
|
||||
doc: &'d BastDoc<'d>,
|
||||
doc: &'d BastDoc,
|
||||
var_sizes: &'d HashMap<String, usize>,
|
||||
fields: Vec<(String, FieldPosition)>,
|
||||
}
|
||||
@@ -227,7 +227,7 @@ impl<'d> BuildCtx<'d> {
|
||||
/// top level).
|
||||
fn walk_struct(
|
||||
&mut self,
|
||||
struct_node: &BastStruct<'d>,
|
||||
struct_node: &BastStruct,
|
||||
prefix: &str,
|
||||
offset: &mut usize,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
@@ -246,7 +246,7 @@ impl<'d> BuildCtx<'d> {
|
||||
/// appending any field paths to `self.fields`.
|
||||
fn walk_field(
|
||||
&mut self,
|
||||
field: &BastField<'d>,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
offset: &mut usize,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
@@ -330,7 +330,7 @@ impl<'d> BuildCtx<'d> {
|
||||
/// supported in v1.
|
||||
fn walk_array(
|
||||
&mut self,
|
||||
array: &BastArray<'d>,
|
||||
array: &BastArray,
|
||||
field_path: &str,
|
||||
offset: &mut usize,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
@@ -394,7 +394,7 @@ impl<'d> BuildCtx<'d> {
|
||||
/// Compute the layout for a `BastUnion` field.
|
||||
fn walk_union(
|
||||
&mut self,
|
||||
union_node: &BastUnion<'d>,
|
||||
union_node: &BastUnion,
|
||||
field_path: &str,
|
||||
offset: &mut usize,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
@@ -415,7 +415,7 @@ impl<'d> BuildCtx<'d> {
|
||||
offset: &mut usize,
|
||||
disc_type: AlkTypeKind,
|
||||
disc_off: usize,
|
||||
union_node: &BastUnion<'d>,
|
||||
union_node: &BastUnion,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
let disc_size = disc_type.type_size().ok_or_else(|| AlkTypeError::Offset {
|
||||
field_path: field_path.to_string(),
|
||||
@@ -488,7 +488,7 @@ impl<'d> BuildCtx<'d> {
|
||||
&mut self,
|
||||
field_path: &str,
|
||||
offset: &mut usize,
|
||||
union_node: &BastUnion<'d>,
|
||||
union_node: &BastUnion,
|
||||
) -> Result<(), AlkTypeError> {
|
||||
let variant_key = format!("{field_path}.{VARIANT_KEY}");
|
||||
let variant_index =
|
||||
|
||||
+3
-2
@@ -9,8 +9,9 @@
|
||||
//!
|
||||
//! - **BAST parser** ([`bast`]): Typed tree over a BAST document —
|
||||
//! `BastDoc`/`BastDef`/`BastStruct`/`BastField`/`BastType`/etc.
|
||||
//! Borrows from the source `serde_json::Value` without cloning field
|
||||
//! data.
|
||||
//! Owns its data (ADR-012 §2a) so consumers (`AlkTypeEngine`,
|
||||
//! `LayoutBuilder`) can store the parsed tree without lifetime
|
||||
//! entanglement; the clone happens once at `BastDoc::new`.
|
||||
//! - **Layout engine** ([`offset_map`], [`layout_builder`],
|
||||
//! [`sequential_reader`], [`read_plan`]): Two layout modes — aligned
|
||||
//! static for mmap-friendly formats, packed sequential for protocol
|
||||
|
||||
+27
-67
@@ -460,7 +460,7 @@ fn materialize_plan_record(
|
||||
/// from the same BAST document. Endianness is read from the root struct's
|
||||
/// `endian` annotation (defaults to little-endian).
|
||||
pub fn materialize_aligned(
|
||||
doc: &BastDoc<'_>,
|
||||
doc: &BastDoc,
|
||||
buffer: &[u8],
|
||||
offset_map: &crate::offset_map::OffsetMap,
|
||||
) -> Result<Value, AlkTypeError> {
|
||||
@@ -480,8 +480,8 @@ pub fn materialize_aligned(
|
||||
|
||||
|
||||
fn materialize_struct_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
struct_node: &BastStruct<'_>,
|
||||
doc: &BastDoc,
|
||||
struct_node: &BastStruct,
|
||||
path_prefix: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -502,8 +502,8 @@ fn materialize_struct_packed(
|
||||
}
|
||||
|
||||
fn materialize_field_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
field: &BastField<'_>,
|
||||
doc: &BastDoc,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -512,13 +512,12 @@ fn materialize_field_packed(
|
||||
let field_endian = field.effective_endian(endian);
|
||||
let ty = field.ty();
|
||||
let resolved = doc.resolve_typeref(ty)?;
|
||||
materialize_typeref_packed(doc, &resolved, field, field_path, field_endian, buffer, offset)
|
||||
materialize_typeref_packed(doc, &resolved, field_path, field_endian, buffer, offset)
|
||||
}
|
||||
|
||||
fn materialize_typeref_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
_field: &BastField<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
field_path: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -626,8 +625,8 @@ fn materialize_typeref_packed(
|
||||
}
|
||||
|
||||
fn materialize_array_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
array: &BastArray<'_>,
|
||||
doc: &BastDoc,
|
||||
array: &BastArray,
|
||||
field_path: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -642,7 +641,6 @@ fn materialize_array_packed(
|
||||
let value = materialize_typeref_packed(
|
||||
doc,
|
||||
&resolved_elem,
|
||||
&dummy_field_for(&resolved_elem),
|
||||
&path,
|
||||
endian,
|
||||
buffer,
|
||||
@@ -654,8 +652,8 @@ fn materialize_array_packed(
|
||||
}
|
||||
|
||||
fn materialize_union_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
union_node: &BastUnion<'_>,
|
||||
doc: &BastDoc,
|
||||
union_node: &BastUnion,
|
||||
field_path: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -709,7 +707,6 @@ fn materialize_union_packed(
|
||||
let variant_value = materialize_typeref_packed(
|
||||
doc,
|
||||
&resolved_variant,
|
||||
&dummy_field_for(&resolved_variant),
|
||||
field_path,
|
||||
endian,
|
||||
buffer,
|
||||
@@ -744,7 +741,6 @@ fn materialize_union_packed(
|
||||
let variant_value = materialize_typeref_packed(
|
||||
doc,
|
||||
&resolved_variant,
|
||||
&dummy_field_for(&resolved_variant),
|
||||
field_path,
|
||||
endian,
|
||||
buffer,
|
||||
@@ -760,8 +756,8 @@ fn materialize_union_packed(
|
||||
}
|
||||
|
||||
fn materialize_record_packed(
|
||||
doc: &BastDoc<'_>,
|
||||
record: &BastRecord<'_>,
|
||||
doc: &BastDoc,
|
||||
record: &BastRecord,
|
||||
field_path: &str,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
@@ -784,7 +780,6 @@ fn materialize_record_packed(
|
||||
let val = materialize_typeref_packed(
|
||||
doc,
|
||||
&resolved_values,
|
||||
&dummy_field_for(&resolved_values),
|
||||
&val_path,
|
||||
endian,
|
||||
buffer,
|
||||
@@ -842,8 +837,8 @@ fn number_from_f64(f: f64, field_path: &str) -> Result<Value, AlkTypeError> {
|
||||
/// sequential walks for composites (Record) whose internal layout can't
|
||||
/// be precomputed into the static offset map.
|
||||
fn materialize_struct_aligned(
|
||||
doc: &BastDoc<'_>,
|
||||
struct_node: &BastStruct<'_>,
|
||||
doc: &BastDoc,
|
||||
struct_node: &BastStruct,
|
||||
path_prefix: &str,
|
||||
offset_map: &crate::offset_map::OffsetMap,
|
||||
endian: Endian,
|
||||
@@ -891,7 +886,6 @@ fn materialize_struct_aligned(
|
||||
materialize_typeref_packed(
|
||||
doc,
|
||||
&resolved,
|
||||
field,
|
||||
&path,
|
||||
field_endian,
|
||||
buffer,
|
||||
@@ -917,8 +911,8 @@ fn materialize_struct_aligned(
|
||||
/// at its `vals[i]` offset-map entry. Array elements are always fixed-size
|
||||
/// in aligned mode (the offset map rejects variable-length elements).
|
||||
fn materialize_array_aligned(
|
||||
doc: &BastDoc<'_>,
|
||||
array: &BastArray<'_>,
|
||||
doc: &BastDoc,
|
||||
array: &BastArray,
|
||||
field_path: &str,
|
||||
offset_map: &crate::offset_map::OffsetMap,
|
||||
endian: Endian,
|
||||
@@ -949,9 +943,9 @@ fn materialize_array_aligned(
|
||||
/// trailing NUL bytes are trimmed.
|
||||
/// - inline length-prefixed (default): a 4-byte length prefix at `start`.
|
||||
fn materialize_variable_aligned(
|
||||
doc: &BastDoc<'_>,
|
||||
field: &BastField<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
field: &BastField,
|
||||
ty: &BastType,
|
||||
field_path: &str,
|
||||
range: &crate::offset_map::ByteRange,
|
||||
endian: Endian,
|
||||
@@ -1010,49 +1004,15 @@ fn materialize_variable_aligned(
|
||||
}
|
||||
|
||||
fn materialize_leaf_at(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
field_path: &str,
|
||||
offset: usize,
|
||||
endian: Endian,
|
||||
buffer: &[u8],
|
||||
) -> Result<Value, AlkTypeError> {
|
||||
let mut pos = offset;
|
||||
materialize_typeref_packed(
|
||||
doc,
|
||||
ty,
|
||||
&dummy_field_for(ty),
|
||||
field_path,
|
||||
endian,
|
||||
buffer,
|
||||
&mut pos,
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct a synthetic `BastField` borrowing from a resolved
|
||||
/// `BastType`'s source node. Used when materializing array elements /
|
||||
/// record values / union variants — these are TypeRefs, not fields, so
|
||||
/// they don't carry field-level annotations. The synthetic field
|
||||
/// inherits no annotations (which is correct: `maxLength`/`encoding`/
|
||||
/// `endian`/`align` are field-level properties, and array elements and
|
||||
/// record values don't have them in BAST).
|
||||
///
|
||||
/// The `BastField::parse` path validates the node; this helper bypasses
|
||||
/// re-validation for a node we've already resolved. The returned field
|
||||
/// borrows from the `BastType`'s source.
|
||||
fn dummy_field_for<'d>(ty: &BastType<'d>) -> BastField<'d> {
|
||||
let source = ty_source(ty);
|
||||
BastField::synthetic(ty.clone(), source)
|
||||
}
|
||||
|
||||
fn ty_source<'d>(ty: &BastType<'d>) -> &'d serde_json::Value {
|
||||
match ty {
|
||||
BastType::Struct(s) => s.source(),
|
||||
BastType::Union(u) => u.source(),
|
||||
BastType::Array(a) => a.source(),
|
||||
BastType::Record(r) => r.source(),
|
||||
_ => &serde_json::Value::Null,
|
||||
}
|
||||
materialize_typeref_packed(doc, ty, field_path, endian, buffer, &mut pos)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1060,13 +1020,13 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> {
|
||||
fn doc_from(root: &Value, name: &str) -> BastDoc {
|
||||
BastDoc::new(root, name).expect("bast doc")
|
||||
}
|
||||
|
||||
fn materialize_packed_strict(
|
||||
root: &Value,
|
||||
doc: &BastDoc<'_>,
|
||||
doc: &BastDoc,
|
||||
buffer: &[u8],
|
||||
) -> Result<Value, AlkTypeError> {
|
||||
let plan = ReadPlan::compile(root, doc.root_name())?;
|
||||
@@ -1457,7 +1417,7 @@ mod tests {
|
||||
// ----- Aligned-mode materialization (M2 fixes) ----------------------
|
||||
|
||||
fn materialize_aligned_strict(
|
||||
doc: &BastDoc<'_>,
|
||||
doc: &BastDoc,
|
||||
buffer: &[u8],
|
||||
) -> Result<Value, AlkTypeError> {
|
||||
let offset_map = crate::offset_map::OffsetMap::compute(doc)?;
|
||||
|
||||
+13
-13
@@ -76,7 +76,7 @@ impl OffsetMap {
|
||||
/// Returns [`AlkTypeError::Offset`] for unsupported type combinations
|
||||
/// encountered during the walk (e.g. unions, which are rejected in
|
||||
/// aligned mode per ADR-008).
|
||||
pub fn compute<'a>(doc: &'a BastDoc<'a>) -> Result<Self, AlkTypeError> {
|
||||
pub fn compute(doc: &BastDoc) -> Result<Self, AlkTypeError> {
|
||||
let root_def = doc.root_def();
|
||||
let struct_node = match root_def.kind() {
|
||||
BastDefKind::Struct(s) => s,
|
||||
@@ -133,7 +133,7 @@ impl OffsetMap {
|
||||
/// reference to the BAST doc for `$ref` resolution. Grouping these
|
||||
/// keeps the recursive helper signatures small.
|
||||
struct ComputeCtx<'d> {
|
||||
doc: &'d BastDoc<'d>,
|
||||
doc: &'d BastDoc,
|
||||
fields: Vec<(String, ByteRange)>,
|
||||
offset: usize,
|
||||
}
|
||||
@@ -158,7 +158,7 @@ impl<'d> ComputeCtx<'d> {
|
||||
/// a natural alignment larger than the default.
|
||||
fn compute_struct(
|
||||
&mut self,
|
||||
struct_node: &BastStruct<'d>,
|
||||
struct_node: &BastStruct,
|
||||
prefix: &str,
|
||||
parent_struct_align: usize,
|
||||
) -> Result<(usize, usize), AlkTypeError> {
|
||||
@@ -210,7 +210,7 @@ impl<'d> ComputeCtx<'d> {
|
||||
/// and appending any field paths to `self.fields`.
|
||||
fn compute_field(
|
||||
&mut self,
|
||||
field: &BastField<'d>,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
struct_default_align: usize,
|
||||
) -> Result<FieldLayout, AlkTypeError> {
|
||||
@@ -253,7 +253,7 @@ impl<'d> ComputeCtx<'d> {
|
||||
fn compute_fixed_field(
|
||||
&mut self,
|
||||
kind: AlkTypeKind,
|
||||
field: &BastField<'d>,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
struct_default_align: usize,
|
||||
) -> Result<FieldLayout, AlkTypeError> {
|
||||
@@ -282,8 +282,8 @@ impl<'d> ComputeCtx<'d> {
|
||||
/// then shifts the nested fields to their final positions.
|
||||
fn compute_struct_field(
|
||||
&mut self,
|
||||
struct_node: &BastStruct<'d>,
|
||||
field: &BastField<'d>,
|
||||
struct_node: &BastStruct,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
struct_default_align: usize,
|
||||
) -> Result<FieldLayout, AlkTypeError> {
|
||||
@@ -321,8 +321,8 @@ impl<'d> ComputeCtx<'d> {
|
||||
/// Compute the layout for a `BastArray` field.
|
||||
fn compute_array_field(
|
||||
&mut self,
|
||||
array: &BastArray<'d>,
|
||||
field: &BastField<'d>,
|
||||
array: &BastArray,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
struct_default_align: usize,
|
||||
) -> Result<FieldLayout, AlkTypeError> {
|
||||
@@ -396,7 +396,7 @@ impl<'d> ComputeCtx<'d> {
|
||||
/// - inline length-prefixing (default): a 4-byte length prefix.
|
||||
fn compute_variable_field(
|
||||
&mut self,
|
||||
field: &BastField<'d>,
|
||||
field: &BastField,
|
||||
field_path: &str,
|
||||
struct_default_align: usize,
|
||||
) -> Result<FieldLayout, AlkTypeError> {
|
||||
@@ -430,7 +430,7 @@ impl<'d> ComputeCtx<'d> {
|
||||
}
|
||||
|
||||
/// If the field's type is a variable-length primitive, return its kind.
|
||||
fn field_variable_kind(field: &BastField<'_>) -> Option<AlkTypeKind> {
|
||||
fn field_variable_kind(field: &BastField) -> Option<AlkTypeKind> {
|
||||
match field.ty() {
|
||||
BastType::Primitive(k) if k.is_variable_length() => Some(*k),
|
||||
_ => None,
|
||||
@@ -439,7 +439,7 @@ fn field_variable_kind(field: &BastField<'_>) -> Option<AlkTypeKind> {
|
||||
|
||||
/// Resolve the field's alignment: field-level `align` annotation,
|
||||
/// then the struct default, then the natural alignment.
|
||||
fn field_alignment(field: &BastField<'_>, struct_default_align: usize, natural: usize) -> usize {
|
||||
fn field_alignment(field: &BastField, struct_default_align: usize, natural: usize) -> usize {
|
||||
if let Some(a) = field.align() {
|
||||
return a.max(1);
|
||||
}
|
||||
@@ -452,7 +452,7 @@ fn field_alignment(field: &BastField<'_>, struct_default_align: usize, natural:
|
||||
/// schemas (BAST field-level annotations live on the field, not the
|
||||
/// element TypeRef), so we fall back to the struct default.
|
||||
fn element_alignment(
|
||||
elem_ty: &BastType<'_>,
|
||||
elem_ty: &BastType,
|
||||
struct_default_align: usize,
|
||||
natural: usize,
|
||||
) -> usize {
|
||||
|
||||
+20
-20
@@ -257,8 +257,8 @@ fn dotted(prefix: &str, name: &str) -> String {
|
||||
}
|
||||
|
||||
fn compile_struct(
|
||||
doc: &BastDoc<'_>,
|
||||
s: &BastStruct<'_>,
|
||||
doc: &BastDoc,
|
||||
s: &BastStruct,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -281,8 +281,8 @@ fn compile_struct(
|
||||
}
|
||||
|
||||
fn compile_field_list(
|
||||
doc: &BastDoc<'_>,
|
||||
fields: &[BastField<'_>],
|
||||
doc: &BastDoc,
|
||||
fields: &[BastField],
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -297,8 +297,8 @@ fn compile_field_list(
|
||||
}
|
||||
|
||||
fn compile_field(
|
||||
doc: &BastDoc<'_>,
|
||||
field: &BastField<'_>,
|
||||
doc: &BastDoc,
|
||||
field: &BastField,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -320,8 +320,8 @@ fn compile_field(
|
||||
/// body, eagerly resolving the ref (with the cycle set guarding the
|
||||
/// definition-level recursion).
|
||||
fn compile_typeref(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -344,8 +344,8 @@ fn compile_typeref(
|
||||
|
||||
/// Compile a dereferenced type node (the `Ref` case never reaches here).
|
||||
fn compile_resolved(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -379,8 +379,8 @@ fn compile_resolved(
|
||||
/// Compile a named definition's kind (the `$ref` target: struct, union,
|
||||
/// or enum — the parser rejects other kinds at `$defs` top level).
|
||||
fn compile_def_kind(
|
||||
doc: &BastDoc<'_>,
|
||||
kind: &BastDefKind<'_>,
|
||||
doc: &BastDoc,
|
||||
kind: &BastDefKind,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -400,8 +400,8 @@ fn compile_def_kind(
|
||||
}
|
||||
|
||||
fn compile_union(
|
||||
doc: &BastDoc<'_>,
|
||||
u: &BastUnion<'_>,
|
||||
doc: &BastDoc,
|
||||
u: &BastUnion,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -462,8 +462,8 @@ fn compile_union(
|
||||
/// union (mirroring the 0.2.0 read loop's `resolve_and_walk_variant`);
|
||||
/// a `$ref` variant resolves through the cycle set.
|
||||
fn compile_variant(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -515,8 +515,8 @@ fn compile_variant(
|
||||
}
|
||||
|
||||
fn compile_array(
|
||||
doc: &BastDoc<'_>,
|
||||
a: &BastArray<'_>,
|
||||
doc: &BastDoc,
|
||||
a: &BastArray,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -537,8 +537,8 @@ fn compile_array(
|
||||
}
|
||||
|
||||
fn compile_record(
|
||||
doc: &BastDoc<'_>,
|
||||
r: &BastRecord<'_>,
|
||||
doc: &BastDoc,
|
||||
r: &BastRecord,
|
||||
container_endian: Endian,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
|
||||
+8
-8
@@ -46,7 +46,7 @@ pub struct UnionDispatch {
|
||||
/// `mapping`.
|
||||
pub fn read_byte_discriminator(
|
||||
buffer: &[u8],
|
||||
union_node: &BastUnion<'_>,
|
||||
union_node: &BastUnion,
|
||||
endian: Endian,
|
||||
) -> Result<UnionDispatch, AlkTypeError> {
|
||||
let (offset, disc_type) = match union_node.discriminator() {
|
||||
@@ -112,12 +112,12 @@ pub fn read_byte_discriminator(
|
||||
/// union's `mapping`.
|
||||
pub fn read_field_discriminator(
|
||||
buffer: &[u8],
|
||||
union_node: &BastUnion<'_>,
|
||||
union_node: &BastUnion,
|
||||
disc_field_offset: usize,
|
||||
endian: Endian,
|
||||
) -> Result<UnionDispatch, AlkTypeError> {
|
||||
let name = match union_node.discriminator() {
|
||||
BastDiscriminator::Field { name } => *name,
|
||||
BastDiscriminator::Field { name } => name.as_str(),
|
||||
BastDiscriminator::Byte { .. } => {
|
||||
return Err(AlkTypeError::Schema(
|
||||
"read_field_discriminator requires a field-name discriminator".to_string(),
|
||||
@@ -193,9 +193,9 @@ pub fn read_field_discriminator(
|
||||
/// - [`AlkTypeError::Schema`] if the union has no `mapping` entries or
|
||||
/// the `key` is not present.
|
||||
pub fn resolve_variant<'a>(
|
||||
union_node: &'a BastUnion<'a>,
|
||||
union_node: &'a BastUnion,
|
||||
key: &str,
|
||||
) -> Result<&'a BastType<'a>, AlkTypeError> {
|
||||
) -> Result<&'a BastType, AlkTypeError> {
|
||||
union_node
|
||||
.variant_for(key)
|
||||
.ok_or_else(|| AlkTypeError::Schema(format!("unknown mapping key: {key}")))
|
||||
@@ -211,7 +211,7 @@ pub fn resolve_variant<'a>(
|
||||
///
|
||||
/// - [`AlkTypeError::Schema`] if the discriminator is a field-name
|
||||
/// discriminator.
|
||||
pub fn discriminator_size(union_node: &BastUnion<'_>) -> Result<usize, AlkTypeError> {
|
||||
pub fn discriminator_size(union_node: &BastUnion) -> Result<usize, AlkTypeError> {
|
||||
match union_node.discriminator() {
|
||||
BastDiscriminator::Byte { disc_type, .. } => match disc_type {
|
||||
AlkTypeKind::Uint8 => Ok(1),
|
||||
@@ -228,7 +228,7 @@ pub fn discriminator_size(union_node: &BastUnion<'_>) -> Result<usize, AlkTypeEr
|
||||
}
|
||||
|
||||
fn verify_mapping_key(
|
||||
union_node: &BastUnion<'_>,
|
||||
union_node: &BastUnion,
|
||||
key: &str,
|
||||
field_path: &str,
|
||||
raw_value: &str,
|
||||
@@ -252,7 +252,7 @@ mod tests {
|
||||
const LE: Endian = Endian::Little;
|
||||
const BE: Endian = Endian::Big;
|
||||
|
||||
fn doc_union<'a>(root: &'a serde_json::Value, name: &'a str) -> BastUnion<'a> {
|
||||
fn doc_union(root: &serde_json::Value, name: &str) -> BastUnion {
|
||||
let doc = BastDoc::new(root, name).expect("bast doc");
|
||||
match doc.root_def().kind() {
|
||||
crate::bast::BastDefKind::Union(u) => u.clone(),
|
||||
|
||||
+11
-11
@@ -111,7 +111,7 @@ impl ValidationPlan {
|
||||
/// nests deeper than the compile depth cap (128) or contains a
|
||||
/// reference cycle (a compile-time rejection of self-recursive
|
||||
/// schemas).
|
||||
pub fn compile(doc: &BastDoc<'_>) -> Result<Self, AlkTypeError> {
|
||||
pub fn compile(doc: &BastDoc) -> Result<Self, AlkTypeError> {
|
||||
let root_def = doc.root_def();
|
||||
let mut seen = BTreeSet::new();
|
||||
let root = match root_def.kind() {
|
||||
@@ -255,7 +255,7 @@ impl ValidVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_enum_node(e: &crate::bast::BastEnum<'_>) -> Result<ValidNode, AlkTypeError> {
|
||||
fn compile_enum_node(e: &crate::bast::BastEnum) -> Result<ValidNode, AlkTypeError> {
|
||||
let count = u64::try_from(e.values().len()).map_err(|_| {
|
||||
AlkTypeError::Schema("bast_validation: enum values len overflows u64".to_string())
|
||||
})?;
|
||||
@@ -280,8 +280,8 @@ fn cycle_err(name: &str, path: &str) -> AlkTypeError {
|
||||
/// `String`/`Bytes` leaves; never crosses a `$ref`, whose target is
|
||||
/// always a struct/union/enum `$defs` entry).
|
||||
fn compile_typeref(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
max_len: Option<usize>,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -311,8 +311,8 @@ fn compile_typeref(
|
||||
|
||||
/// Compile a dereferenced type node (the `Ref` case never reaches here).
|
||||
fn compile_resolved(
|
||||
doc: &BastDoc<'_>,
|
||||
ty: &BastType<'_>,
|
||||
doc: &BastDoc,
|
||||
ty: &BastType,
|
||||
max_len: Option<usize>,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
@@ -367,8 +367,8 @@ fn compile_resolved(
|
||||
}
|
||||
|
||||
fn compile_struct(
|
||||
doc: &BastDoc<'_>,
|
||||
s: &BastStruct<'_>,
|
||||
doc: &BastDoc,
|
||||
s: &BastStruct,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
seen: &mut BTreeSet<String>,
|
||||
@@ -400,8 +400,8 @@ fn compile_struct(
|
||||
}
|
||||
|
||||
fn compile_union(
|
||||
doc: &BastDoc<'_>,
|
||||
u: &BastUnion<'_>,
|
||||
doc: &BastDoc,
|
||||
u: &BastUnion,
|
||||
path: &str,
|
||||
depth: usize,
|
||||
seen: &mut BTreeSet<String>,
|
||||
@@ -692,7 +692,7 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> {
|
||||
fn doc_from(root: &Value, name: &str) -> BastDoc {
|
||||
BastDoc::new(root, name).expect("bast doc")
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ fn sftp_like_byte_union_doc() -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn union_of<'a>(root: &'a serde_json::Value, name: &'a str) -> BastUnion<'a> {
|
||||
fn union_of(root: &serde_json::Value, name: &str) -> BastUnion {
|
||||
let doc = BastDoc::new(root, name).expect("bast doc");
|
||||
match doc.root_def().kind() {
|
||||
BastDefKind::Union(u) => u.clone(),
|
||||
|
||||
Reference in new issue
Block a user