Skip to main content

verit_core/
schema.rs

1//! Schema model, builder, canonical binary encoding ("VSC1"), and content
2//! hashing. The canonical form sorts types by name, fields by ID, and enum
3//! variants by value, so one logical schema has exactly one byte encoding and
4//! therefore exactly one id.
5
6use crate::error::{Error, Result};
7use crate::layout::{self, StructLayout};
8use crate::value::Value;
9
10pub const SCHEMA_MAGIC: &[u8; 4] = b"VSC1";
11
12/// Maximum `list<…>` nesting depth. Bounds recursion in schema decode and in
13/// resolver plan-building so a hostile schema cannot overflow the stack. Far
14/// above any real schema (a handful of levels).
15pub const MAX_TYPE_DEPTH: u32 = 64;
16
17/// A field or element type. `Struct`/`Enum` reference other types in the same
18/// schema by index into the canonically (name-)sorted type table.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum Type {
21    Bool,
22    U8,
23    U16,
24    U32,
25    U64,
26    I8,
27    I16,
28    I32,
29    I64,
30    F32,
31    F64,
32    String,
33    Bytes,
34    Struct(u16),
35    Enum(u16),
36    List(Box<Type>),
37    /// `map<K, V>`: an ordered set of (key, value) entries, entries sorted by
38    /// key in canonical order so one logical map has exactly one encoding. `K`
39    /// is restricted to a comparable key type (see [`Type::is_valid_map_key`]).
40    Map(Box<Type>, Box<Type>),
41    /// `union<T0, T1, …>`: a tagged sum. A value is a `u32` tag (an index into
42    /// the variant list, which is positional and therefore part of the type's
43    /// identity) followed by that variant's value.
44    Union(Vec<Type>),
45}
46
47impl Type {
48    pub fn describe(&self, schema: &Schema) -> String {
49        match self {
50            Type::Struct(i) => format!("struct {}", schema.type_name(*i)),
51            Type::Enum(i) => format!("enum {}", schema.type_name(*i)),
52            Type::List(e) => format!("list<{}>", e.describe(schema)),
53            Type::Map(k, v) => format!("map<{}, {}>", k.describe(schema), v.describe(schema)),
54            Type::Union(variants) => {
55                let parts: Vec<String> = variants.iter().map(|t| t.describe(schema)).collect();
56                format!("union<{}>", parts.join(", "))
57            }
58            other => format!("{other:?}").to_lowercase(),
59        }
60    }
61
62    /// Legal `map` key types: booleans, integers, `string`, and enums — types
63    /// with a total, well-defined canonical order. Floats (NaN), bytes, and
64    /// composite types are not usable as keys.
65    pub fn is_valid_map_key(&self) -> bool {
66        matches!(
67            self,
68            Type::Bool
69                | Type::U8
70                | Type::U16
71                | Type::U32
72                | Type::U64
73                | Type::I8
74                | Type::I16
75                | Type::I32
76                | Type::I64
77                | Type::String
78                | Type::Enum(_)
79        )
80    }
81}
82
83/// A scalar **custom default** for a field: the value a reader synthesizes when
84/// the field is absent (see [`StructReader::get_or_default`]). v1 supports
85/// scalar defaults only; floats are stored as their bit patterns so the schema
86/// types stay `Eq`.
87///
88/// [`StructReader::get_or_default`]: crate::StructReader::get_or_default
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum Default {
91    Bool(bool),
92    U8(u8),
93    U16(u16),
94    U32(u32),
95    U64(u64),
96    I8(i8),
97    I16(i16),
98    I32(i32),
99    I64(i64),
100    /// f32 default, stored as `to_bits()`.
101    F32(u32),
102    /// f64 default, stored as `to_bits()`.
103    F64(u64),
104    Enum(u32),
105}
106
107impl Default {
108    /// The number of little-endian bytes this default occupies in the canonical
109    /// schema (matches its scalar type's slot size).
110    fn wire_size(&self) -> usize {
111        match self {
112            Default::Bool(_) | Default::U8(_) | Default::I8(_) => 1,
113            Default::U16(_) | Default::I16(_) => 2,
114            Default::U32(_) | Default::I32(_) | Default::F32(_) | Default::Enum(_) => 4,
115            Default::U64(_) | Default::I64(_) | Default::F64(_) => 8,
116        }
117    }
118
119    /// The default's value as low-`wire_size` little-endian bytes.
120    fn to_bits(self) -> u64 {
121        match self {
122            Default::Bool(b) => b as u64,
123            Default::U8(x) => x as u64,
124            Default::U16(x) => x as u64,
125            Default::U32(x) => x as u64,
126            Default::U64(x) => x,
127            Default::I8(x) => x as u8 as u64,
128            Default::I16(x) => x as u16 as u64,
129            Default::I32(x) => x as u32 as u64,
130            Default::I64(x) => x as u64,
131            Default::F32(bits) => bits as u64,
132            Default::F64(bits) => bits,
133            Default::Enum(x) => x as u64,
134        }
135    }
136
137    /// Reconstruct a default for scalar field type `ty` from `bits` (the low
138    /// bytes read from the schema). Returns `None` if `ty` is not a scalar that
139    /// can carry a default.
140    fn from_bits(ty: &Type, bits: u64) -> Option<Default> {
141        Some(match ty {
142            Type::Bool => Default::Bool(bits != 0),
143            Type::U8 => Default::U8(bits as u8),
144            Type::U16 => Default::U16(bits as u16),
145            Type::U32 => Default::U32(bits as u32),
146            Type::U64 => Default::U64(bits),
147            Type::I8 => Default::I8(bits as i8),
148            Type::I16 => Default::I16(bits as i16),
149            Type::I32 => Default::I32(bits as i32),
150            Type::I64 => Default::I64(bits as i64),
151            Type::F32 => Default::F32(bits as u32),
152            Type::F64 => Default::F64(bits),
153            Type::Enum(_) => Default::Enum(bits as u32),
154            _ => return None,
155        })
156    }
157
158    /// The scalar `Type` this default is for (used to validate it against the
159    /// field). `None` for `Enum` (an enum default matches any enum field).
160    fn scalar_type(&self) -> Option<Type> {
161        Some(match self {
162            Default::Bool(_) => Type::Bool,
163            Default::U8(_) => Type::U8,
164            Default::U16(_) => Type::U16,
165            Default::U32(_) => Type::U32,
166            Default::U64(_) => Type::U64,
167            Default::I8(_) => Type::I8,
168            Default::I16(_) => Type::I16,
169            Default::I32(_) => Type::I32,
170            Default::I64(_) => Type::I64,
171            Default::F32(_) => Type::F32,
172            Default::F64(_) => Type::F64,
173            Default::Enum(_) => return None,
174        })
175    }
176}
177
178#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct FieldDef {
180    pub id: u16,
181    pub name: String,
182    pub ty: Type,
183    /// Optional scalar default (see [`Default`]).
184    pub default: Option<Default>,
185}
186
187/// How a struct's fields are stored.
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub enum StructMode {
190    /// Presence bitmap + a fixed slot for every field, present or not
191    /// (canonical kind byte 0). Best general default.
192    Sparse,
193    /// No bitmap; every field always present and mandatory (kind byte 2).
194    /// Smallest and fastest for records with no optionality.
195    Dense,
196    /// Presence bitmap + slots for **present fields only**, packed in
197    /// size-class order (kind byte 3). Field offsets are recovered in O(1)
198    /// with popcount rank queries over the bitmap — sparse-data sizes without
199    /// per-object vtables and without giving up zero-copy. At most 64 fields.
200    Packed,
201}
202
203#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct StructDef {
205    pub name: String,
206    /// Sorted by `id`, strictly ascending.
207    pub fields: Vec<FieldDef>,
208    pub mode: StructMode,
209}
210
211#[derive(Clone, Debug, PartialEq, Eq)]
212pub struct EnumDef {
213    pub name: String,
214    /// (value, name), sorted by value, strictly ascending. Enums are open:
215    /// unknown values round-trip as raw numbers.
216    pub variants: Vec<(u32, String)>,
217}
218
219impl EnumDef {
220    pub fn name_of(&self, value: u32) -> Option<&str> {
221        self.variants
222            .binary_search_by_key(&value, |(v, _)| *v)
223            .ok()
224            .map(|i| self.variants[i].1.as_str())
225    }
226}
227
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum TypeDef {
230    Struct(StructDef),
231    Enum(EnumDef),
232}
233
234impl TypeDef {
235    pub fn name(&self) -> &str {
236        match self {
237            TypeDef::Struct(s) => &s.name,
238            TypeDef::Enum(e) => &e.name,
239        }
240    }
241}
242
243impl StructDef {
244    pub fn is_dense(&self) -> bool {
245        self.mode == StructMode::Dense
246    }
247
248    pub fn is_packed(&self) -> bool {
249        self.mode == StructMode::Packed
250    }
251}
252
253/// A validated schema: canonical type table, cached canonical bytes, content
254/// id, and precomputed struct layouts.
255#[derive(Clone, Debug)]
256pub struct Schema {
257    types: Vec<TypeDef>,
258    root: u16,
259    canonical: Vec<u8>,
260    id: u128,
261    layouts: Vec<Option<StructLayout>>,
262}
263
264impl Schema {
265    /// 128-bit content hash of the canonical schema bytes (truncated SHA-256,
266    /// see [`crate::hash`]). Acts as a content-address / cache key; wide
267    /// enough (~2^64 birthday resistance) to resist schema-confusion.
268    pub fn id(&self) -> u128 {
269        self.id
270    }
271
272    pub fn canonical_bytes(&self) -> &[u8] {
273        &self.canonical
274    }
275
276    pub fn root_index(&self) -> u16 {
277        self.root
278    }
279
280    pub fn type_count(&self) -> u16 {
281        self.types.len() as u16
282    }
283
284    pub fn type_def(&self, index: u16) -> Option<&TypeDef> {
285        self.types.get(index as usize)
286    }
287
288    pub fn type_name(&self, index: u16) -> &str {
289        self.types
290            .get(index as usize)
291            .map(|t| t.name())
292            .unwrap_or("<bad type index>")
293    }
294
295    /// Panics on a non-struct index: schema validation guarantees every
296    /// `Type::Struct(i)` reference points at a struct def.
297    pub(crate) fn struct_def_unchecked(&self, index: u16) -> &StructDef {
298        match &self.types[index as usize] {
299            TypeDef::Struct(s) => s,
300            TypeDef::Enum(_) => panic!("schema invariant: type {index} is not a struct"),
301        }
302    }
303
304    pub(crate) fn enum_def_unchecked(&self, index: u16) -> &EnumDef {
305        match &self.types[index as usize] {
306            TypeDef::Enum(e) => e,
307            TypeDef::Struct(_) => panic!("schema invariant: type {index} is not an enum"),
308        }
309    }
310
311    pub(crate) fn layout_unchecked(&self, index: u16) -> &StructLayout {
312        self.layouts[index as usize]
313            .as_ref()
314            .expect("schema invariant: struct type has a layout")
315    }
316
317    pub(crate) fn packed_layout_unchecked(&self, index: u16) -> &crate::layout::PackedLayout {
318        self.layout_unchecked(index).as_packed()
319    }
320
321    /// Position (in ID-sorted order) and definition of a field, by ID.
322    pub fn find_field(&self, struct_index: u16, id: u16) -> Option<(usize, &FieldDef)> {
323        let sd = match self.type_def(struct_index)? {
324            TypeDef::Struct(s) => s,
325            TypeDef::Enum(_) => return None,
326        };
327        sd.fields
328            .binary_search_by_key(&id, |f| f.id)
329            .ok()
330            .map(|pos| (pos, &sd.fields[pos]))
331    }
332
333    /// Decode a schema from its canonical bytes (e.g. the inline schema
334    /// region of a message). Rejects malformed and non-canonical encodings.
335    pub fn from_canonical(bytes: &[u8]) -> Result<Schema> {
336        let mut cur = Cur { b: bytes, p: 0 };
337        let magic = cur.take(4)?;
338        if magic != SCHEMA_MAGIC {
339            return Err(Error::BadSchema("bad VSC1 magic".into()));
340        }
341        let type_count = cur.u16()?;
342        let mut types = Vec::with_capacity(type_count as usize);
343        for _ in 0..type_count {
344            let kind = cur.u8()?;
345            let name = cur.name()?;
346            match kind {
347                0 | 2 | 3 => {
348                    let field_count = cur.u16()?;
349                    let mut fields = Vec::with_capacity(field_count as usize);
350                    for _ in 0..field_count {
351                        let id = cur.u16()?;
352                        let fname = cur.name()?;
353                        let ty = cur.type_expr(type_count)?;
354                        fields.push(FieldDef {
355                            id,
356                            name: fname,
357                            ty,
358                            default: None,
359                        });
360                    }
361                    let mode = match kind {
362                        2 => StructMode::Dense,
363                        3 => StructMode::Packed,
364                        _ => StructMode::Sparse,
365                    };
366                    types.push(TypeDef::Struct(StructDef { name, fields, mode }));
367                }
368                1 => {
369                    let variant_count = cur.u16()?;
370                    let mut variants = Vec::with_capacity(variant_count as usize);
371                    for _ in 0..variant_count {
372                        let value = cur.u32()?;
373                        let vname = cur.name()?;
374                        variants.push((value, vname));
375                    }
376                    types.push(TypeDef::Enum(EnumDef { name, variants }));
377                }
378                k => return Err(Error::BadSchema(format!("unknown type kind {k}"))),
379            }
380        }
381        let root = cur.u16()?;
382        // Optional defaults section (present only if there are trailing bytes).
383        if cur.p < bytes.len() {
384            let count = cur.u16()?;
385            // The encoder omits the section entirely when there are no defaults,
386            // so a present-but-empty section (count == 0) is a non-canonical
387            // encoding of a default-less schema. Reject it: exactly one byte
388            // string per schema.
389            if count == 0 {
390                return Err(Error::BadSchema(
391                    "empty defaults section must be omitted, not encoded as count 0".into(),
392                ));
393            }
394            for _ in 0..count {
395                let ti = cur.u16()? as usize;
396                let fid = cur.u16()?;
397                let ty = match types.get(ti) {
398                    Some(TypeDef::Struct(sd)) => {
399                        sd.fields.iter().find(|f| f.id == fid).map(|f| f.ty.clone())
400                    }
401                    _ => None,
402                }
403                .ok_or_else(|| Error::BadSchema("default references unknown field".into()))?;
404                let size = default_wire_size(&ty)
405                    .ok_or_else(|| Error::BadSchema("default on a non-scalar field".into()))?;
406                let raw = cur.take(size)?;
407                let mut word = [0u8; 8];
408                word[..size].copy_from_slice(raw);
409                let d = Default::from_bits(&ty, u64::from_le_bytes(word))
410                    .ok_or_else(|| Error::BadSchema("default on a non-scalar field".into()))?;
411                if let Some(TypeDef::Struct(sd)) = types.get_mut(ti) {
412                    if let Some(f) = sd.fields.iter_mut().find(|f| f.id == fid) {
413                        f.default = Some(d);
414                    }
415                }
416            }
417        }
418        if cur.p != bytes.len() {
419            return Err(Error::BadSchema("trailing bytes after schema".into()));
420        }
421        let schema = Schema::assemble(types, root)?;
422        // Strict canonicity: a decoded schema must re-encode to *exactly* the
423        // bytes it came from. The structural checks above (name/id sort order,
424        // no empty defaults section) reject most non-canonical forms, but not
425        // every one — e.g. a scalar default whose bytes are a non-canonical
426        // encoding of its value (a `bool` default byte other than 0/1, high
427        // bits set past the type), or defaults-section entries out of canonical
428        // order or duplicated (last-wins on decode). Comparing against the
429        // re-encoding closes the whole class at once, enforcing the format's
430        // one-schema-id ⇔ one-byte-string contract that the content address and
431        // the schema fuzz oracle both rely on.
432        if schema.canonical_bytes() != bytes {
433            return Err(Error::BadSchema(
434                "schema is not in canonical form (re-encoding differs)".into(),
435            ));
436        }
437        Ok(schema)
438    }
439
440    /// Validate, canonically encode, hash, and compute layouts.
441    fn assemble(types: Vec<TypeDef>, root: u16) -> Result<Schema> {
442        validate(&types, root)?;
443        let canonical = encode_canonical(&types, root);
444        let id = crate::hash::schema_id(&canonical);
445        let layouts = types
446            .iter()
447            .map(|t| match t {
448                TypeDef::Struct(s) => Some(layout::compute(&s.fields, s.mode)),
449                TypeDef::Enum(_) => None,
450            })
451            .collect();
452        Ok(Schema {
453            types,
454            root,
455            canonical,
456            id,
457            layouts,
458        })
459    }
460}
461
462fn validate(types: &[TypeDef], root: u16) -> Result<()> {
463    if types.is_empty() {
464        return Err(Error::BadSchema("schema has no types".into()));
465    }
466    // Types strictly ascending by name (canonical order, implies uniqueness).
467    for w in types.windows(2) {
468        if w[0].name() >= w[1].name() {
469            return Err(Error::BadSchema(format!(
470                "types not in canonical (name-sorted) order: {:?} then {:?}",
471                w[0].name(),
472                w[1].name()
473            )));
474        }
475    }
476    fn check_type(types: &[TypeDef], t: &Type, depth: u32) -> Result<()> {
477        if depth > MAX_TYPE_DEPTH {
478            return Err(Error::BadSchema(format!(
479                "type nesting exceeds limit of {MAX_TYPE_DEPTH}"
480            )));
481        }
482        match t {
483            Type::Struct(i) => match types.get(*i as usize) {
484                Some(TypeDef::Struct(_)) => Ok(()),
485                _ => Err(Error::BadSchema(format!("type ref {i} is not a struct"))),
486            },
487            Type::Enum(i) => match types.get(*i as usize) {
488                Some(TypeDef::Enum(_)) => Ok(()),
489                _ => Err(Error::BadSchema(format!("type ref {i} is not an enum"))),
490            },
491            Type::List(e) => check_type(types, e, depth + 1),
492            Type::Map(k, v) => {
493                if !k.is_valid_map_key() {
494                    return Err(Error::BadSchema(format!(
495                        "map key type {k:?} is not a valid key (use bool, an integer, string, or an enum)"
496                    )));
497                }
498                check_type(types, k, depth + 1)?;
499                check_type(types, v, depth + 1)
500            }
501            Type::Union(variants) => {
502                if variants.is_empty() {
503                    return Err(Error::BadSchema("union has no variants".into()));
504                }
505                for v in variants {
506                    check_type(types, v, depth + 1)?;
507                }
508                Ok(())
509            }
510            _ => Ok(()),
511        }
512    }
513    let check_ref = |ty: &Type| -> Result<()> { check_type(types, ty, 0) };
514    for td in types {
515        match td {
516            TypeDef::Struct(s) => {
517                if s.name.is_empty() {
518                    return Err(Error::BadSchema("empty type name".into()));
519                }
520                if s.mode == StructMode::Packed && s.fields.len() > 64 {
521                    return Err(Error::BadSchema(format!(
522                        "packed struct {} has {} fields; packed structs are \
523                         limited to 64 (the bitmap must fit one u64 rank word)",
524                        s.name,
525                        s.fields.len()
526                    )));
527                }
528                for w in s.fields.windows(2) {
529                    if w[0].id >= w[1].id {
530                        return Err(Error::BadSchema(format!(
531                            "fields of {} not strictly ascending by id",
532                            s.name
533                        )));
534                    }
535                }
536                for f in &s.fields {
537                    if f.name.is_empty() {
538                        return Err(Error::BadSchema(format!("empty field name in {}", s.name)));
539                    }
540                    check_ref(&f.ty)?;
541                    if let Some(d) = f.default {
542                        let ok = match d.scalar_type() {
543                            Some(t) => f.ty == t,
544                            None => matches!(f.ty, Type::Enum(_)), // Default::Enum
545                        };
546                        if !ok {
547                            return Err(Error::BadSchema(format!(
548                                "field {} in {} has a default whose type does not match the field",
549                                f.name, s.name
550                            )));
551                        }
552                    }
553                }
554            }
555            TypeDef::Enum(e) => {
556                if e.name.is_empty() {
557                    return Err(Error::BadSchema("empty type name".into()));
558                }
559                for w in e.variants.windows(2) {
560                    if w[0].0 >= w[1].0 {
561                        return Err(Error::BadSchema(format!(
562                            "variants of {} not strictly ascending by value",
563                            e.name
564                        )));
565                    }
566                }
567            }
568        }
569    }
570    match types.get(root as usize) {
571        Some(TypeDef::Struct(_)) => Ok(()),
572        Some(TypeDef::Enum(_)) => Err(Error::BadSchema("root type must be a struct".into())),
573        None => Err(Error::BadSchema("root type index out of range".into())),
574    }
575}
576
577// ---------------------------------------------------------------------------
578// Canonical encoding
579// ---------------------------------------------------------------------------
580
581fn push_u16(b: &mut Vec<u8>, v: u16) {
582    b.extend_from_slice(&v.to_le_bytes());
583}
584
585fn push_u32(b: &mut Vec<u8>, v: u32) {
586    b.extend_from_slice(&v.to_le_bytes());
587}
588
589fn push_name(b: &mut Vec<u8>, s: &str) {
590    push_u16(b, s.len() as u16);
591    b.extend_from_slice(s.as_bytes());
592}
593
594fn push_type(b: &mut Vec<u8>, ty: &Type) {
595    match ty {
596        Type::Bool => b.push(0x01),
597        Type::U8 => b.push(0x02),
598        Type::U16 => b.push(0x03),
599        Type::U32 => b.push(0x04),
600        Type::U64 => b.push(0x05),
601        Type::I8 => b.push(0x06),
602        Type::I16 => b.push(0x07),
603        Type::I32 => b.push(0x08),
604        Type::I64 => b.push(0x09),
605        Type::F32 => b.push(0x0A),
606        Type::F64 => b.push(0x0B),
607        Type::String => b.push(0x10),
608        Type::Bytes => b.push(0x11),
609        Type::Struct(i) => {
610            b.push(0x20);
611            push_u16(b, *i);
612        }
613        Type::Enum(i) => {
614            b.push(0x21);
615            push_u16(b, *i);
616        }
617        Type::List(e) => {
618            b.push(0x22);
619            push_type(b, e);
620        }
621        Type::Map(k, v) => {
622            b.push(0x23);
623            push_type(b, k);
624            push_type(b, v);
625        }
626        Type::Union(variants) => {
627            b.push(0x24);
628            push_u16(b, variants.len() as u16);
629            for v in variants {
630                push_type(b, v);
631            }
632        }
633    }
634}
635
636fn encode_canonical(types: &[TypeDef], root: u16) -> Vec<u8> {
637    let mut b = Vec::new();
638    b.extend_from_slice(SCHEMA_MAGIC);
639    push_u16(&mut b, types.len() as u16);
640    for td in types {
641        match td {
642            TypeDef::Struct(s) => {
643                b.push(match s.mode {
644                    StructMode::Sparse => 0,
645                    StructMode::Dense => 2,
646                    StructMode::Packed => 3,
647                });
648                push_name(&mut b, &s.name);
649                push_u16(&mut b, s.fields.len() as u16);
650                for f in &s.fields {
651                    push_u16(&mut b, f.id);
652                    push_name(&mut b, &f.name);
653                    push_type(&mut b, &f.ty);
654                }
655            }
656            TypeDef::Enum(e) => {
657                b.push(1);
658                push_name(&mut b, &e.name);
659                push_u16(&mut b, e.variants.len() as u16);
660                for (v, n) in &e.variants {
661                    push_u32(&mut b, *v);
662                    push_name(&mut b, n);
663                }
664            }
665        }
666    }
667    push_u16(&mut b, root);
668    // Defaults section — appended ONLY if some field carries a default, so a
669    // schema without defaults is byte-identical to the pre-defaults format
670    // (existing ids and golden vectors are unchanged). Entries are in canonical
671    // order (types are name-sorted, fields id-sorted).
672    let mut defaults: Vec<(u16, u16, Default)> = Vec::new();
673    for (ti, td) in types.iter().enumerate() {
674        if let TypeDef::Struct(sd) = td {
675            for f in &sd.fields {
676                if let Some(d) = f.default {
677                    defaults.push((ti as u16, f.id, d));
678                }
679            }
680        }
681    }
682    if !defaults.is_empty() {
683        push_u16(&mut b, defaults.len() as u16);
684        for (ti, fid, d) in defaults {
685            push_u16(&mut b, ti);
686            push_u16(&mut b, fid);
687            let bytes = d.to_bits().to_le_bytes();
688            b.extend_from_slice(&bytes[..d.wire_size()]);
689        }
690    }
691    b
692}
693
694/// Byte width of a default for scalar type `ty`, or `None` if `ty` cannot carry
695/// a default.
696fn default_wire_size(ty: &Type) -> Option<usize> {
697    Some(match ty {
698        Type::Bool | Type::U8 | Type::I8 => 1,
699        Type::U16 | Type::I16 => 2,
700        Type::U32 | Type::I32 | Type::F32 | Type::Enum(_) => 4,
701        Type::U64 | Type::I64 | Type::F64 => 8,
702        _ => return None,
703    })
704}
705
706// ---------------------------------------------------------------------------
707// Canonical decoding cursor
708// ---------------------------------------------------------------------------
709
710struct Cur<'a> {
711    b: &'a [u8],
712    p: usize,
713}
714
715impl<'a> Cur<'a> {
716    fn take(&mut self, n: usize) -> Result<&'a [u8]> {
717        let end = self
718            .p
719            .checked_add(n)
720            .ok_or_else(|| Error::BadSchema("length overflow".into()))?;
721        let s = self
722            .b
723            .get(self.p..end)
724            .ok_or_else(|| Error::BadSchema("schema truncated".into()))?;
725        self.p = end;
726        Ok(s)
727    }
728
729    fn u8(&mut self) -> Result<u8> {
730        Ok(self.take(1)?[0])
731    }
732
733    fn u16(&mut self) -> Result<u16> {
734        Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
735    }
736
737    fn u32(&mut self) -> Result<u32> {
738        Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
739    }
740
741    fn name(&mut self) -> Result<String> {
742        let len = self.u16()? as usize;
743        let bytes = self.take(len)?;
744        String::from_utf8(bytes.to_vec())
745            .map_err(|_| Error::BadSchema("name is not valid UTF-8".into()))
746    }
747
748    fn type_expr(&mut self, type_count: u16) -> Result<Type> {
749        self.type_expr_depth(type_count, 0)
750    }
751
752    /// Decode a type expression, bounding list nesting so a hostile inline
753    /// schema (e.g. thousands of `list<` bytes) cannot overflow the stack.
754    fn type_expr_depth(&mut self, type_count: u16, depth: u32) -> Result<Type> {
755        if depth > MAX_TYPE_DEPTH {
756            return Err(Error::BadSchema(format!(
757                "type nesting exceeds limit of {MAX_TYPE_DEPTH}"
758            )));
759        }
760        let code = self.u8()?;
761        Ok(match code {
762            0x01 => Type::Bool,
763            0x02 => Type::U8,
764            0x03 => Type::U16,
765            0x04 => Type::U32,
766            0x05 => Type::U64,
767            0x06 => Type::I8,
768            0x07 => Type::I16,
769            0x08 => Type::I32,
770            0x09 => Type::I64,
771            0x0A => Type::F32,
772            0x0B => Type::F64,
773            0x10 => Type::String,
774            0x11 => Type::Bytes,
775            0x20 => {
776                let i = self.u16()?;
777                if i >= type_count {
778                    return Err(Error::BadSchema("struct type index out of range".into()));
779                }
780                Type::Struct(i)
781            }
782            0x21 => {
783                let i = self.u16()?;
784                if i >= type_count {
785                    return Err(Error::BadSchema("enum type index out of range".into()));
786                }
787                Type::Enum(i)
788            }
789            0x22 => Type::List(Box::new(self.type_expr_depth(type_count, depth + 1)?)),
790            0x23 => {
791                let key = self.type_expr_depth(type_count, depth + 1)?;
792                let value = self.type_expr_depth(type_count, depth + 1)?;
793                Type::Map(Box::new(key), Box::new(value))
794            }
795            0x24 => {
796                let count = self.u16()?;
797                if count == 0 {
798                    return Err(Error::BadSchema("union has no variants".into()));
799                }
800                let mut variants = Vec::with_capacity(count as usize);
801                for _ in 0..count {
802                    variants.push(self.type_expr_depth(type_count, depth + 1)?);
803                }
804                Type::Union(variants)
805            }
806            c => return Err(Error::BadSchema(format!("unknown type code {c:#04x}"))),
807        })
808    }
809}
810
811// ---------------------------------------------------------------------------
812// Builder
813// ---------------------------------------------------------------------------
814
815/// Draft type used in [`SchemaBuilder`]: like [`Type`] but names other types
816/// by string instead of index (indices only exist after canonical sorting).
817#[derive(Clone, Debug)]
818pub enum Dt {
819    Bool,
820    U8,
821    U16,
822    U32,
823    U64,
824    I8,
825    I16,
826    I32,
827    I64,
828    F32,
829    F64,
830    Str,
831    Bytes,
832    Named(String),
833    List(Box<Dt>),
834    Map(Box<Dt>, Box<Dt>),
835    Union(Vec<Dt>),
836}
837
838impl Dt {
839    pub fn named(name: &str) -> Dt {
840        Dt::Named(name.to_string())
841    }
842
843    pub fn list(elem: Dt) -> Dt {
844        Dt::List(Box::new(elem))
845    }
846
847    pub fn map(key: Dt, value: Dt) -> Dt {
848        Dt::Map(Box::new(key), Box::new(value))
849    }
850
851    pub fn union(variants: Vec<Dt>) -> Dt {
852        Dt::Union(variants)
853    }
854}
855
856enum DraftDef {
857    Struct(Vec<(u16, String, Dt)>, StructMode),
858    Enum(Vec<(u32, String)>),
859}
860
861/// Declaration order never matters: `build` sorts types by name and fields by
862/// ID before encoding, so equivalent declarations produce identical schema ids.
863pub struct SchemaBuilder {
864    types: Vec<(String, DraftDef)>,
865    defaults: Vec<(String, u16, Value)>,
866}
867
868impl SchemaBuilder {
869    #[allow(clippy::new_without_default)]
870    pub fn new() -> SchemaBuilder {
871        SchemaBuilder {
872            types: Vec::new(),
873            defaults: Vec::new(),
874        }
875    }
876
877    /// Give a scalar field a **custom default**: the value a reader synthesizes
878    /// via [`StructReader::get_or_default`] when the field is absent. `default`
879    /// must be a scalar [`Value`] matching the field's type (`Value::Enum` for
880    /// an enum field). Applied at [`build`](SchemaBuilder::build).
881    ///
882    /// [`StructReader::get_or_default`]: crate::StructReader::get_or_default
883    pub fn set_default(
884        mut self,
885        struct_name: &str,
886        field_id: u16,
887        default: Value,
888    ) -> SchemaBuilder {
889        self.defaults
890            .push((struct_name.to_string(), field_id, default));
891        self
892    }
893
894    pub fn add_struct(mut self, name: &str, fields: Vec<(u16, &str, Dt)>) -> SchemaBuilder {
895        self.push_struct(name, fields, StructMode::Sparse);
896        self
897    }
898
899    /// A dense struct stores no presence bitmap: every field is always
900    /// present (encoding with one missing is an error). Smaller and slightly
901    /// faster to read; use for records whose fields are all mandatory, e.g.
902    /// geometric points, samples, matrix rows.
903    pub fn add_dense_struct(mut self, name: &str, fields: Vec<(u16, &str, Dt)>) -> SchemaBuilder {
904        self.push_struct(name, fields, StructMode::Dense);
905        self
906    }
907
908    /// A packed struct stores a presence bitmap followed by slots for the
909    /// *present* fields only. Field offsets are recovered in O(1) with
910    /// popcount rank queries over the bitmap — sparse-data wire sizes without
911    /// giving up zero-copy random access or paying for per-object vtables.
912    /// Limited to 64 fields. Best for wide records that are usually sparse
913    /// (events with many optional attributes, config with many defaults).
914    pub fn add_packed_struct(mut self, name: &str, fields: Vec<(u16, &str, Dt)>) -> SchemaBuilder {
915        self.push_struct(name, fields, StructMode::Packed);
916        self
917    }
918
919    fn push_struct(&mut self, name: &str, fields: Vec<(u16, &str, Dt)>, mode: StructMode) {
920        self.types.push((
921            name.to_string(),
922            DraftDef::Struct(
923                fields
924                    .into_iter()
925                    .map(|(id, n, t)| (id, n.to_string(), t))
926                    .collect(),
927                mode,
928            ),
929        ));
930    }
931
932    pub fn add_enum(mut self, name: &str, variants: Vec<(u32, &str)>) -> SchemaBuilder {
933        self.types.push((
934            name.to_string(),
935            DraftDef::Enum(
936                variants
937                    .into_iter()
938                    .map(|(v, n)| (v, n.to_string()))
939                    .collect(),
940            ),
941        ));
942        self
943    }
944
945    pub fn build(mut self, root: &str) -> Result<Schema> {
946        if self.types.len() > u16::MAX as usize {
947            return Err(Error::BadSchema("too many types".into()));
948        }
949        self.types.sort_by(|a, b| a.0.cmp(&b.0));
950        for w in self.types.windows(2) {
951            if w[0].0 == w[1].0 {
952                return Err(Error::BadSchema(format!(
953                    "duplicate type name {:?}",
954                    w[0].0
955                )));
956            }
957        }
958        let index_of = |name: &str| -> Result<u16> {
959            self.types
960                .binary_search_by(|(n, _)| n.as_str().cmp(name))
961                .map(|i| i as u16)
962                .map_err(|_| Error::BadSchema(format!("unknown type name {name:?}")))
963        };
964        let resolve = |dt: &Dt| -> Result<Type> {
965            fn go(
966                types: &[(String, DraftDef)],
967                index_of: &dyn Fn(&str) -> Result<u16>,
968                dt: &Dt,
969            ) -> Result<Type> {
970                Ok(match dt {
971                    Dt::Bool => Type::Bool,
972                    Dt::U8 => Type::U8,
973                    Dt::U16 => Type::U16,
974                    Dt::U32 => Type::U32,
975                    Dt::U64 => Type::U64,
976                    Dt::I8 => Type::I8,
977                    Dt::I16 => Type::I16,
978                    Dt::I32 => Type::I32,
979                    Dt::I64 => Type::I64,
980                    Dt::F32 => Type::F32,
981                    Dt::F64 => Type::F64,
982                    Dt::Str => Type::String,
983                    Dt::Bytes => Type::Bytes,
984                    Dt::Named(n) => {
985                        let i = index_of(n)?;
986                        match &types[i as usize].1 {
987                            DraftDef::Struct(..) => Type::Struct(i),
988                            DraftDef::Enum(_) => Type::Enum(i),
989                        }
990                    }
991                    Dt::List(e) => Type::List(Box::new(go(types, index_of, e)?)),
992                    Dt::Map(k, v) => Type::Map(
993                        Box::new(go(types, index_of, k)?),
994                        Box::new(go(types, index_of, v)?),
995                    ),
996                    Dt::Union(variants) => {
997                        let mut out = Vec::with_capacity(variants.len());
998                        for v in variants {
999                            out.push(go(types, index_of, v)?);
1000                        }
1001                        Type::Union(out)
1002                    }
1003                })
1004            }
1005            go(&self.types, &index_of, dt)
1006        };
1007
1008        let mut types = Vec::with_capacity(self.types.len());
1009        for (name, draft) in &self.types {
1010            match draft {
1011                DraftDef::Struct(fields, mode) => {
1012                    if fields.len() > u16::MAX as usize {
1013                        return Err(Error::BadSchema(format!("too many fields in {name}")));
1014                    }
1015                    let mut fds = Vec::with_capacity(fields.len());
1016                    for (id, fname, dt) in fields {
1017                        if fname.len() > u16::MAX as usize || name.len() > u16::MAX as usize {
1018                            return Err(Error::BadSchema("name too long".into()));
1019                        }
1020                        fds.push(FieldDef {
1021                            id: *id,
1022                            name: fname.clone(),
1023                            ty: resolve(dt)?,
1024                            default: None,
1025                        });
1026                    }
1027                    fds.sort_by_key(|f| f.id);
1028                    for w in fds.windows(2) {
1029                        if w[0].id == w[1].id {
1030                            return Err(Error::BadSchema(format!(
1031                                "duplicate field id {} in {name}",
1032                                w[0].id
1033                            )));
1034                        }
1035                    }
1036                    types.push(TypeDef::Struct(StructDef {
1037                        name: name.clone(),
1038                        fields: fds,
1039                        mode: *mode,
1040                    }));
1041                }
1042                DraftDef::Enum(variants) => {
1043                    let mut vs = variants.clone();
1044                    vs.sort_by_key(|(v, _)| *v);
1045                    for w in vs.windows(2) {
1046                        if w[0].0 == w[1].0 {
1047                            return Err(Error::BadSchema(format!(
1048                                "duplicate variant value {} in {name}",
1049                                w[0].0
1050                            )));
1051                        }
1052                    }
1053                    types.push(TypeDef::Enum(EnumDef {
1054                        name: name.clone(),
1055                        variants: vs,
1056                    }));
1057                }
1058            }
1059        }
1060        // Apply custom defaults (types are name-sorted; validate() will reject
1061        // a default whose type does not match its field).
1062        for (sname, fid, value) in &self.defaults {
1063            let d = value_to_default(value).ok_or_else(|| {
1064                Error::BadSchema(format!("default for {sname} field {fid} is not a scalar"))
1065            })?;
1066            let td = types
1067                .iter_mut()
1068                .find(|t| t.name() == sname)
1069                .ok_or_else(|| {
1070                    Error::BadSchema(format!("default references unknown type {sname:?}"))
1071                })?;
1072            match td {
1073                TypeDef::Struct(sd) => {
1074                    let f = sd.fields.iter_mut().find(|f| f.id == *fid).ok_or_else(|| {
1075                        Error::BadSchema(format!(
1076                            "default references unknown field {fid} in {sname}"
1077                        ))
1078                    })?;
1079                    f.default = Some(d);
1080                }
1081                TypeDef::Enum(_) => {
1082                    return Err(Error::BadSchema(format!(
1083                        "cannot set a default on enum type {sname}"
1084                    )))
1085                }
1086            }
1087        }
1088        let root_idx = index_of(root)?;
1089        Schema::assemble(types, root_idx)
1090    }
1091}
1092
1093/// Convert a scalar [`Value`] to a [`Default`], or `None` for a non-scalar.
1094fn value_to_default(v: &Value) -> Option<Default> {
1095    Some(match v {
1096        Value::Bool(b) => Default::Bool(*b),
1097        Value::U8(x) => Default::U8(*x),
1098        Value::U16(x) => Default::U16(*x),
1099        Value::U32(x) => Default::U32(*x),
1100        Value::U64(x) => Default::U64(*x),
1101        Value::I8(x) => Default::I8(*x),
1102        Value::I16(x) => Default::I16(*x),
1103        Value::I32(x) => Default::I32(*x),
1104        Value::I64(x) => Default::I64(*x),
1105        Value::F32(x) => Default::F32(x.to_bits()),
1106        Value::F64(x) => Default::F64(x.to_bits()),
1107        Value::Enum(x) => Default::Enum(*x),
1108        _ => return None,
1109    })
1110}