Skip to main content

verit_core/
derive.rs

1//! Runtime support for `#[derive(Verit)]` — the Rust peer of Python's `@verit`
2//! decorator.
3//!
4//! The `verit-derive` proc-macro generates an impl of [`VeritType`] for a
5//! struct: it builds the struct's [`Schema`] from the field types (and every
6//! nested `#[derive(Verit)]` type it references), then rides the dynamic
7//! encoder and reader for `to_verit` / `from_verit`. The macro lives in a
8//! separate, feature-gated crate so `verit`'s default build keeps **zero
9//! dependencies** (syn/quote are build-only, off by default). This trait is
10//! plain Rust and always present — it costs nothing when the macro is unused.
11//!
12//! The wire contract is the schema's 128-bit content id: a Rust `#[derive(Verit)]`
13//! type, a Python `@verit` class, and a hand-written `.vsc` IDL that describe
14//! the same fields all produce the same id, so their bytes interoperate.
15
16use std::collections::BTreeSet;
17
18use crate::encode::{encode, SchemaMode};
19use crate::message::{Message, StructReader};
20use crate::resolve::Resolver;
21use crate::schema::{Schema, SchemaBuilder, StructMode};
22use crate::value::Value;
23use crate::Result;
24
25/// Implemented by every `#[derive(Verit)]` type. All of it is generated; you
26/// never write an impl by hand. The provided methods ([`to_verit`],
27/// [`from_verit`], [`verit_schema_id`]) are the surface you actually call.
28///
29/// [`to_verit`]: VeritType::to_verit
30/// [`from_verit`]: VeritType::from_verit
31/// [`verit_schema_id`]: VeritType::verit_schema_id
32pub trait VeritType: Sized {
33    /// This type's name in the generated schema (its Rust type name).
34    const VERIT_NAME: &'static str;
35
36    /// How this struct is stored on the wire (`#[verit(mode = "…")]`).
37    const VERIT_MODE: StructMode;
38
39    /// Add this type's struct definition — and, transitively, every nested
40    /// `VeritType` it references — to `builder`, skipping any name already in
41    /// `seen`. The derive generates this; it is the mechanism that lets one
42    /// root type pull its whole type graph into a single schema.
43    fn verit_register(builder: SchemaBuilder, seen: &mut BTreeSet<&'static str>) -> SchemaBuilder;
44
45    /// Pack `self` into a dynamic struct [`Value`] (the encoder's input form).
46    fn verit_pack(&self) -> Value;
47
48    /// Reconstruct `Self` from a dynamic [`StructReader`] over this type's
49    /// fields.
50    fn verit_unpack(reader: &StructReader) -> Result<Self>;
51
52    /// The process-wide [`Schema`] rooted at this type, built once and cached.
53    /// Generated with a per-type `OnceLock`, so the schema (and its 128-bit id)
54    /// is computed at most once per program run.
55    fn verit_schema() -> &'static Schema;
56
57    /// This type's 128-bit schema id — the cross-language wire contract.
58    fn verit_schema_id() -> u128 {
59        Self::verit_schema().id()
60    }
61
62    /// Encode `self` to Veritate bytes against this type's schema.
63    /// `SchemaMode::Inline` embeds the schema (self-describing, `verit dump`-able);
64    /// `SchemaMode::HashOnly` writes just the 128-bit id.
65    fn to_verit(&self, mode: SchemaMode) -> Result<Vec<u8>> {
66        encode(Self::verit_schema(), &self.verit_pack(), mode)
67    }
68
69    /// Decode `Self` from bytes written against this same type's schema
70    /// (matching the Python decorator's identity-resolver behaviour). To read
71    /// bytes written by an *evolved* schema, resolve explicitly with
72    /// [`Resolver::new`] and call [`verit_unpack`](VeritType::verit_unpack).
73    fn from_verit(bytes: &[u8]) -> Result<Self> {
74        let schema = Self::verit_schema();
75        let resolver = Resolver::identity(schema)?;
76        let msg = Message::parse(bytes)?;
77        let root = msg.root(&resolver)?;
78        Self::verit_unpack(&root)
79    }
80}