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}