Skip to main content

vox_schema/
lib.rs

1//! Canonical schema model and wire payload types for vox.
2//!
3//! This crate contains the transport-independent schema data model, content
4//! hashing, and CBOR schema payload helpers shared by vox runtimes and codecs.
5
6use facet::{Facet, OpaqueSerialize, PtrConst};
7use facet_core::Shape;
8use std::collections::HashMap;
9
10// ============================================================================
11// Schema data types
12// ============================================================================
13
14/// A content hash that uniquely identifies a type's postcard-level structure.
15///
16/// Computed via blake3, truncated to 64 bits. The same type always produces
17/// the same TypeSchemaId regardless of connection, session, process, or
18/// language.
19// r[impl schema.type-id]
20#[derive(Facet, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
21#[facet(transparent)]
22pub struct SchemaHash(pub u64);
23
24/// Temporary index assigned during schema extraction to handle cycles in
25/// recursive types. Completely unrelated to type parameters — this is purely
26/// a bookkeeping index for the extraction/hashing pipeline.
27#[derive(Facet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
28pub struct CycleSchemaIndex(u64);
29
30impl CycleSchemaIndex {
31    /// The starting index for a fresh extraction pass.
32    pub fn first() -> Self {
33        Self(1)
34    }
35
36    /// Return the current index and advance to the next one.
37    pub fn next(&mut self) -> Self {
38        let current = *self;
39        self.0 += 1;
40        current
41    }
42}
43
44/// The name of a generic type parameter (e.g. `"T"`, `"K"`, `"V"`).
45///
46/// Used in two places:
47/// - `Schema::type_params` — declares the parameter names for a generic type
48/// - `TypeRef::Var` — references a parameter by name at usage sites
49///
50/// Cannot be constructed outside this module — the only legitimate source
51/// is facet's `TypeParam::name`.
52#[derive(Facet, Clone, PartialEq, Eq, Hash, Debug)]
53#[facet(transparent)]
54pub struct TypeParamName(pub String);
55
56impl TypeParamName {
57    /// Get the type parameter name as a string slice.
58    pub fn as_str(&self) -> &str {
59        &self.0
60    }
61}
62
63/// A reference to a type in a schema. Either a concrete type (with optional
64/// type arguments for generics) or a type variable bound by the enclosing
65/// generic's `type_params`.
66///
67/// Generic over the ID type: `TypeSchemaId` for final schemas,
68/// `MixedId` during extraction.
69#[derive(Facet, Clone, Debug, PartialEq, Eq, Hash)]
70#[repr(u8)]
71#[facet(tag = "tag", rename_all = "snake_case")]
72pub enum TypeRef<Id = SchemaHash> {
73    /// A concrete type, possibly generic.
74    Concrete {
75        type_id: Id,
76        /// Type arguments for generic types. Empty for non-generic types.
77        args: Vec<TypeRef<Id>>,
78    },
79    /// A reference to a type parameter of the enclosing generic type,
80    /// by name (e.g. `TypeParamName("T")`).
81    Var { name: TypeParamName },
82}
83
84impl<Id> TypeRef<Id> {
85    /// Shorthand for a non-generic concrete type reference.
86    pub fn concrete(type_id: Id) -> Self {
87        TypeRef::Concrete {
88            type_id,
89            args: Vec::new(),
90        }
91    }
92
93    /// Shorthand for a generic concrete type reference with type arguments.
94    pub fn generic(type_id: Id, args: Vec<TypeRef<Id>>) -> Self {
95        TypeRef::Concrete { type_id, args }
96    }
97
98    /// Collect all concrete IDs reachable from this TypeRef (depth-first).
99    pub fn collect_ids(&self, out: &mut Vec<Id>)
100    where
101        Id: Copy,
102    {
103        match self {
104            TypeRef::Concrete { type_id, args } => {
105                out.push(*type_id);
106                for arg in args {
107                    arg.collect_ids(out);
108                }
109            }
110            TypeRef::Var { .. } => {}
111        }
112    }
113
114    /// Return the concrete type ID if this is a non-generic `Concrete` variant, panicking otherwise.
115    pub fn expect_concrete_id(&self) -> &Id {
116        match self {
117            TypeRef::Concrete { type_id, args } if args.is_empty() => type_id,
118            TypeRef::Concrete { .. } => panic!("TypeRef::expect_concrete_id: has type args"),
119            TypeRef::Var { .. } => panic!("TypeRef::expect_concrete_id: is a type variable"),
120        }
121    }
122
123    /// Map a `TypeRef<Id>` to `TypeRef<OtherId>` by applying `f` to every concrete ID.
124    pub fn map<OtherId, F: Fn(Id) -> OtherId + Copy>(self, f: F) -> TypeRef<OtherId> {
125        match self {
126            TypeRef::Concrete { type_id, args } => TypeRef::Concrete {
127                type_id: f(type_id),
128                args: args.into_iter().map(|a| a.map(f)).collect(),
129            },
130            TypeRef::Var { name } => TypeRef::Var { name },
131        }
132    }
133
134    /// Fallible version of `map` — applies `f` to every concrete ID, propagating errors.
135    pub fn try_map<OtherId, E, F: Fn(Id) -> Result<OtherId, E> + Copy>(
136        self,
137        f: &F,
138    ) -> Result<TypeRef<OtherId>, E> {
139        match self {
140            TypeRef::Concrete { type_id, args } => Ok(TypeRef::Concrete {
141                type_id: f(type_id)?,
142                args: args
143                    .into_iter()
144                    .map(|a| a.try_map(f))
145                    .collect::<Result<_, _>>()?,
146            }),
147            TypeRef::Var { name } => Ok(TypeRef::Var { name }),
148        }
149    }
150}
151
152impl TypeRef {
153    /// Look up the schema for this TypeRef in the registry and return
154    /// the schema's kind with all type variables substituted.
155    ///
156    /// For non-generic types (`Concrete { args: [] }`), returns the kind as-is.
157    /// For generic types, substitutes `Var` references with the concrete
158    /// type arguments from this TypeRef.
159    ///
160    /// Returns `None` if the schema is not in the registry or this is a `Var`.
161    pub fn resolve_kind(&self, registry: &SchemaRegistry) -> Option<SchemaKind> {
162        match self {
163            TypeRef::Var { .. } => None,
164            TypeRef::Concrete { type_id, args } => {
165                let schema = registry.get(type_id)?;
166                if args.is_empty() {
167                    return Some(schema.kind.clone());
168                }
169                // Build substitution map: type param name → concrete TypeRef
170                let subst: HashMap<&TypeParamName, &TypeRef> =
171                    schema.type_params.iter().zip(args.iter()).collect();
172                let kind = schema
173                    .kind
174                    .clone()
175                    .try_map_type_refs(&mut |tr| -> Result<TypeRef, std::convert::Infallible> {
176                        Ok(match tr {
177                            TypeRef::Var { ref name } => match subst.get(name) {
178                                Some(concrete) => (*concrete).clone(),
179                                None => tr,
180                            },
181                            other => other,
182                        })
183                    })
184                    .unwrap(); // infallible
185                Some(kind)
186            }
187        }
188    }
189}
190
191/// During extraction, IDs can be either already-finalized content hashes
192/// or temporary indices that will be resolved during finalization.
193#[derive(Facet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
194#[repr(u8)]
195pub enum MixedId {
196    /// A final content hash (from a previously extracted type).
197    Final(SchemaHash),
198    /// A temporary index assigned during the current extraction pass.
199    /// Used only for cycle detection/resolution during hashing.
200    Temp(CycleSchemaIndex),
201}
202
203/// The root schema type, generic over the ID representation.
204#[derive(Facet, Clone, Debug)]
205pub struct Schema<Id = SchemaHash> {
206    /// A unique identifier for this schema (hash of its contents)
207    pub id: Id,
208
209    /// Type parameter names for generic types. Empty for non-generic types.
210    #[facet(default)]
211    pub type_params: Vec<TypeParamName>,
212
213    /// The inner description of the schema, if it's a struct, an enum, etc.
214    pub kind: SchemaKind<Id>,
215}
216
217impl Schema {
218    /// Returns the type name for nominal types (struct/enum), or `None` for
219    /// structural types (tuple, list, map, etc.).
220    pub fn name(&self) -> Option<&str> {
221        match &self.kind {
222            SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => Some(name.as_str()),
223            _ => None,
224        }
225    }
226}
227
228/// The structural kind of a type, generic over the ID representation.
229#[derive(Facet, Clone, Debug)]
230#[repr(u8)]
231#[facet(tag = "tag", rename_all = "snake_case")]
232pub enum SchemaKind<Id = SchemaHash> {
233    Struct {
234        /// The type name (e.g. "Point"). Used for matching across schema
235        /// versions and for diagnostics. MUST NOT be empty.
236        name: String,
237        fields: Vec<FieldSchema<Id>>,
238    },
239    Enum {
240        /// The type name (e.g. "Color"). Used for matching across schema
241        /// versions and for diagnostics. MUST NOT be empty.
242        name: String,
243        variants: Vec<VariantSchema<Id>>,
244    },
245    Tuple {
246        elements: Vec<TypeRef<Id>>,
247    },
248    List {
249        element: TypeRef<Id>,
250    },
251    Map {
252        key: TypeRef<Id>,
253        value: TypeRef<Id>,
254    },
255    Array {
256        element: TypeRef<Id>,
257        length: u64,
258    },
259    Option {
260        element: TypeRef<Id>,
261    },
262    Channel {
263        direction: ChannelDirection,
264        element: TypeRef<Id>,
265    },
266    Primitive {
267        primitive_type: PrimitiveType,
268    },
269}
270
271impl<Id> SchemaKind<Id> {
272    /// Visit every TypeRef in this schema kind.
273    pub fn for_each_type_ref(&self, f: &mut impl FnMut(&TypeRef<Id>)) {
274        match self {
275            Self::Primitive { .. } => {}
276            Self::Struct { fields, .. } => {
277                for field in fields {
278                    field.for_each_type_ref(f);
279                }
280            }
281            Self::Enum { variants, .. } => {
282                for variant in variants {
283                    variant.for_each_type_ref(f);
284                }
285            }
286            Self::Tuple { elements } => {
287                for elem in elements {
288                    f(elem);
289                }
290            }
291            Self::List { element }
292            | Self::Option { element }
293            | Self::Array { element, .. }
294            | Self::Channel { element, .. } => f(element),
295            Self::Map { key, value } => {
296                f(key);
297                f(value);
298            }
299        }
300    }
301
302    /// Transform every TypeRef in this schema kind, with fallible mapping.
303    pub fn try_map_type_refs<OtherId, E>(
304        self,
305        f: &mut impl FnMut(TypeRef<Id>) -> Result<TypeRef<OtherId>, E>,
306    ) -> Result<SchemaKind<OtherId>, E> {
307        Ok(match self {
308            Self::Primitive { primitive_type } => SchemaKind::Primitive { primitive_type },
309            Self::Struct { name, fields } => SchemaKind::Struct {
310                name,
311                fields: fields
312                    .into_iter()
313                    .map(|field| field.try_map_type_ref(f))
314                    .collect::<Result<_, _>>()?,
315            },
316            Self::Enum { name, variants } => SchemaKind::Enum {
317                name,
318                variants: variants
319                    .into_iter()
320                    .map(|v| v.try_map_type_refs(f))
321                    .collect::<Result<_, _>>()?,
322            },
323            Self::Tuple { elements } => SchemaKind::Tuple {
324                elements: elements.into_iter().map(f).collect::<Result<_, _>>()?,
325            },
326            Self::List { element } => SchemaKind::List {
327                element: f(element)?,
328            },
329            Self::Map { key, value } => SchemaKind::Map {
330                key: f(key)?,
331                value: f(value)?,
332            },
333            Self::Array { element, length } => SchemaKind::Array {
334                element: f(element)?,
335                length,
336            },
337            Self::Option { element } => SchemaKind::Option {
338                element: f(element)?,
339            },
340            Self::Channel { direction, element } => SchemaKind::Channel {
341                direction,
342                element: f(element)?,
343            },
344        })
345    }
346}
347
348impl<Id> FieldSchema<Id> {
349    /// Visit the TypeRef in this field.
350    pub fn for_each_type_ref(&self, f: &mut impl FnMut(&TypeRef<Id>)) {
351        f(&self.type_ref);
352    }
353
354    /// Transform the TypeRef in this field.
355    pub fn try_map_type_ref<OtherId, E>(
356        self,
357        f: &mut impl FnMut(TypeRef<Id>) -> Result<TypeRef<OtherId>, E>,
358    ) -> Result<FieldSchema<OtherId>, E> {
359        Ok(FieldSchema {
360            name: self.name,
361            type_ref: f(self.type_ref)?,
362            required: self.required,
363        })
364    }
365}
366
367impl<Id> VariantSchema<Id> {
368    /// Visit every TypeRef in this variant.
369    pub fn for_each_type_ref(&self, f: &mut impl FnMut(&TypeRef<Id>)) {
370        self.payload.for_each_type_ref(f);
371    }
372
373    /// Transform every TypeRef in this variant.
374    pub fn try_map_type_refs<OtherId, E>(
375        self,
376        f: &mut impl FnMut(TypeRef<Id>) -> Result<TypeRef<OtherId>, E>,
377    ) -> Result<VariantSchema<OtherId>, E> {
378        Ok(VariantSchema {
379            name: self.name,
380            index: self.index,
381            payload: self.payload.try_map_type_refs(f)?,
382        })
383    }
384}
385
386impl<Id> VariantPayload<Id> {
387    /// Visit every TypeRef in this payload.
388    pub fn for_each_type_ref(&self, f: &mut impl FnMut(&TypeRef<Id>)) {
389        match self {
390            Self::Unit => {}
391            Self::Newtype { type_ref } => f(type_ref),
392            Self::Tuple { types } => {
393                for t in types {
394                    f(t);
395                }
396            }
397            Self::Struct { fields } => {
398                for field in fields {
399                    field.for_each_type_ref(f);
400                }
401            }
402        }
403    }
404
405    /// Transform every TypeRef in this payload.
406    pub fn try_map_type_refs<OtherId, E>(
407        self,
408        f: &mut impl FnMut(TypeRef<Id>) -> Result<TypeRef<OtherId>, E>,
409    ) -> Result<VariantPayload<OtherId>, E> {
410        Ok(match self {
411            Self::Unit => VariantPayload::Unit,
412            Self::Newtype { type_ref } => VariantPayload::Newtype {
413                type_ref: f(type_ref)?,
414            },
415            Self::Tuple { types } => VariantPayload::Tuple {
416                types: types.into_iter().map(f).collect::<Result<_, _>>()?,
417            },
418            Self::Struct { fields } => VariantPayload::Struct {
419                fields: fields
420                    .into_iter()
421                    .map(|field| field.try_map_type_ref(f))
422                    .collect::<Result<_, _>>()?,
423            },
424        })
425    }
426}
427
428/// The direction of a channel type.
429#[derive(Facet, Clone, Copy, PartialEq, Eq, Debug)]
430#[repr(u8)]
431#[facet(tag = "tag", rename_all = "snake_case")]
432pub enum ChannelDirection {
433    /// A sending channel (`Tx<T>`).
434    Tx,
435    /// A receiving channel (`Rx<T>`).
436    Rx,
437}
438
439/// Type aliases for schemas during extraction (mixed temp/final IDs).
440pub type MixedSchema = Schema<MixedId>;
441pub type MixedSchemaKind = SchemaKind<MixedId>;
442
443/// Describes a single field in a struct or struct variant.
444#[derive(Facet, Clone, Debug)]
445pub struct FieldSchema<Id = SchemaHash> {
446    pub name: String,
447    pub type_ref: TypeRef<Id>,
448    pub required: bool,
449}
450
451/// Describes a single variant in an enum.
452#[derive(Facet, Clone, Debug)]
453pub struct VariantSchema<Id = SchemaHash> {
454    pub name: String,
455    pub index: u32,
456    pub payload: VariantPayload<Id>,
457}
458
459/// The payload of an enum variant.
460#[derive(Facet, Clone, Debug)]
461#[repr(u8)]
462#[facet(tag = "tag", rename_all = "snake_case")]
463pub enum VariantPayload<Id = SchemaHash> {
464    Unit,
465    Newtype { type_ref: TypeRef<Id> },
466    Tuple { types: Vec<TypeRef<Id>> },
467    Struct { fields: Vec<FieldSchema<Id>> },
468}
469
470/// Primitive types supported by the wire format.
471#[derive(Facet, Clone, Copy, PartialEq, Eq, Debug)]
472#[repr(u8)]
473#[facet(tag = "tag", rename_all = "snake_case")]
474pub enum PrimitiveType {
475    Bool,
476    U8,
477    U16,
478    U32,
479    U64,
480    U128,
481    I8,
482    I16,
483    I32,
484    I64,
485    I128,
486    F32,
487    F64,
488    Char,
489    String,
490    Unit,
491    Never,
492    Bytes,
493    /// An opaque payload — a length-prefixed byte sequence whose
494    /// length prefix is a little-endian u32 (not a varint like other
495    /// postcard sequences).
496    Payload,
497}
498
499// ============================================================================
500// Content hashing — r[schema.type-id.hash]
501// ============================================================================
502
503impl PrimitiveType {
504    /// The tag string used for hashing this primitive type.
505    fn hash_tag(self) -> &'static str {
506        match self {
507            PrimitiveType::Bool => "bool",
508            PrimitiveType::U8 => "u8",
509            PrimitiveType::U16 => "u16",
510            PrimitiveType::U32 => "u32",
511            PrimitiveType::U64 => "u64",
512            PrimitiveType::U128 => "u128",
513            PrimitiveType::I8 => "i8",
514            PrimitiveType::I16 => "i16",
515            PrimitiveType::I32 => "i32",
516            PrimitiveType::I64 => "i64",
517            PrimitiveType::I128 => "i128",
518            PrimitiveType::F32 => "f32",
519            PrimitiveType::F64 => "f64",
520            PrimitiveType::Char => "char",
521            PrimitiveType::String => "string",
522            PrimitiveType::Unit => "unit",
523            PrimitiveType::Never => "never",
524            PrimitiveType::Bytes => "bytes",
525            PrimitiveType::Payload => "payload",
526        }
527    }
528}
529
530/// Context for computing content hashes of schemas.
531///
532/// Generic over the ID type so it works with both `MixedId` (during extraction)
533/// and `TypeSchemaId` (for already-finalized schemas).
534struct SchemaHasher<'a, Id: Copy> {
535    hasher: blake3::Hasher,
536    resolve: &'a dyn Fn(Id) -> SchemaHash,
537}
538
539impl<'a, Id: Copy> SchemaHasher<'a, Id> {
540    fn new(resolve: &'a dyn Fn(Id) -> SchemaHash) -> Self {
541        Self {
542            hasher: blake3::Hasher::new(),
543            resolve,
544        }
545    }
546
547    fn feed_string(&mut self, s: &str) {
548        self.hasher.update(&(s.len() as u32).to_le_bytes());
549        self.hasher.update(s.as_bytes());
550    }
551
552    fn feed_type_ref(&mut self, tr: &TypeRef<Id>) {
553        match tr {
554            TypeRef::Concrete { type_id, args } => {
555                self.feed_string("concrete");
556                let resolved = (self.resolve)(*type_id);
557                self.hasher.update(&resolved.0.to_le_bytes());
558                if !args.is_empty() {
559                    self.feed_string("args");
560                    for arg in args {
561                        self.feed_type_ref(arg);
562                    }
563                }
564            }
565            TypeRef::Var { name } => {
566                self.feed_string("var");
567                self.feed_string(&name.0);
568            }
569        }
570    }
571
572    // r[impl schema.type-id.hash.primitives]
573    // r[impl schema.type-id.hash.struct]
574    // r[impl schema.type-id.hash.enum]
575    // r[impl schema.type-id.hash.container]
576    // r[impl schema.type-id.hash.tuple]
577    fn feed_schema(&mut self, kind: &SchemaKind<Id>, type_params: &[TypeParamName]) {
578        match kind {
579            SchemaKind::Primitive { primitive_type } => {
580                self.feed_string(primitive_type.hash_tag());
581            }
582            SchemaKind::Struct { name, fields } => {
583                self.feed_string("struct");
584                self.feed_string(name);
585                self.hasher
586                    .update(&(type_params.len() as u32).to_le_bytes());
587                for tp in type_params {
588                    self.feed_string(&tp.0);
589                }
590                for field in fields {
591                    self.feed_string(&field.name);
592                    self.feed_type_ref(&field.type_ref);
593                }
594            }
595            SchemaKind::Enum { name, variants } => {
596                self.feed_string("enum");
597                self.feed_string(name);
598                self.hasher
599                    .update(&(type_params.len() as u32).to_le_bytes());
600                for tp in type_params {
601                    self.feed_string(&tp.0);
602                }
603                for variant in variants {
604                    self.feed_string(&variant.name);
605                    self.hasher.update(&variant.index.to_le_bytes());
606                    match &variant.payload {
607                        VariantPayload::Unit => {
608                            self.feed_string("unit");
609                        }
610                        VariantPayload::Newtype { type_ref } => {
611                            self.feed_string("newtype");
612                            self.feed_type_ref(type_ref);
613                        }
614                        VariantPayload::Tuple { types } => {
615                            self.feed_string("tuple");
616                            for tr in types {
617                                self.feed_type_ref(tr);
618                            }
619                        }
620                        VariantPayload::Struct { fields } => {
621                            self.feed_string("struct");
622                            for field in fields {
623                                self.feed_string(&field.name);
624                                self.feed_type_ref(&field.type_ref);
625                            }
626                        }
627                    }
628                }
629            }
630            SchemaKind::Tuple { elements } => {
631                self.feed_string("tuple");
632                for elem in elements {
633                    self.feed_type_ref(elem);
634                }
635            }
636            SchemaKind::List { element } => {
637                self.feed_string("list");
638                self.feed_type_ref(element);
639            }
640            SchemaKind::Map { key, value } => {
641                self.feed_string("map");
642                self.feed_type_ref(key);
643                self.feed_type_ref(value);
644            }
645            SchemaKind::Array { element, length } => {
646                self.feed_string("array");
647                self.feed_type_ref(element);
648                self.hasher.update(&length.to_le_bytes());
649            }
650            SchemaKind::Option { element } => {
651                self.feed_string("option");
652                self.feed_type_ref(element);
653            }
654            SchemaKind::Channel { direction, element } => {
655                self.feed_string("channel");
656                self.feed_string(match direction {
657                    ChannelDirection::Tx => "send",
658                    ChannelDirection::Rx => "recv",
659                });
660                self.feed_type_ref(element);
661            }
662        }
663    }
664
665    fn finalize(self) -> SchemaHash {
666        let hash = self.hasher.finalize();
667        let bytes: [u8; 8] = hash.as_bytes()[0..8].try_into().expect("slice len");
668        SchemaHash(u64::from_le_bytes(bytes))
669    }
670}
671
672/// Compute the content hash of a schema, given a resolver for child type IDs.
673pub fn compute_content_hash<Id: Copy>(
674    kind: &SchemaKind<Id>,
675    type_params: &[TypeParamName],
676    resolve: &dyn Fn(Id) -> SchemaHash,
677) -> SchemaHash {
678    let mut hasher = SchemaHasher::new(resolve);
679    hasher.feed_schema(kind, type_params);
680    hasher.finalize()
681}
682
683/// Collect all TypeSchemaIds directly referenced by a SchemaKind.
684pub fn schema_child_ids(kind: &SchemaKind) -> Vec<SchemaHash> {
685    let mut refs = Vec::new();
686    kind.for_each_type_ref(&mut |tr| tr.collect_ids(&mut refs));
687    refs
688}
689
690/// CBOR-encoded schema payload (schemas + method bindings).
691///
692/// Newtype over `Vec<u8>` so the type system distinguishes raw bytes from
693/// CBOR-encoded schema data. Empty when no new schemas need to be sent.
694#[derive(Facet, Clone, Debug, Default)]
695#[repr(transparent)]
696#[facet(transparent)]
697pub struct CborPayload(pub Vec<u8>);
698
699impl CborPayload {
700    pub fn is_empty(&self) -> bool {
701        self.0.is_empty()
702    }
703}
704
705/// Lookup table mapping TypeSchemaId → Schema, used for resolving type
706/// references during deserialization with translation plans.
707pub type SchemaRegistry = HashMap<SchemaHash, Schema>;
708
709/// Build a SchemaRegistry from a list of schemas.
710pub fn build_registry(schemas: &[Schema]) -> SchemaRegistry {
711    schemas.iter().map(|s| (s.id, s.clone())).collect()
712}
713
714/// Anything that can look up schemas by their content hash.
715///
716/// Implemented by SchemaRegistry (HashMap), the operation store, etc.
717/// Used by the send tracker to source schemas without caring where they
718/// come from.
719pub trait SchemaSource {
720    fn get_schema(&self, id: SchemaHash) -> Option<Schema>;
721}
722
723impl SchemaSource for SchemaRegistry {
724    fn get_schema(&self, id: SchemaHash) -> Option<Schema> {
725        self.get(&id).cloned()
726    }
727}
728
729/// Whether a method schema binding describes args or the response type.
730#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
731#[repr(u8)]
732#[facet(tag = "tag", rename_all = "snake_case")]
733pub enum BindingDirection {
734    /// The sender will send data of this type as method arguments.
735    Args,
736
737    /// The sender will send data of this type as the method response.
738    Response,
739}
740
741/// CBOR-encoded payload inside a schema wire message.
742/// A struct so new fields can be added without breaking the wire format.
743#[derive(Facet, Clone, Debug)]
744pub struct SchemaPayload {
745    /// All schemas we're sending over. Sending the schema twice is a
746    /// protocol error: peers must bail out.
747    pub schemas: Vec<Schema>,
748
749    /// Hash of the schema of the type corresponding to this method.
750    /// When attached to `RequestCall`, this is the args tuple, e.g.
751    /// for `add(a: u32, b: u32) -> u64` it's `(u32, u32)`.
752    /// For `RequestResponse` it's `u64` in thea bove example.
753    pub root: TypeRef,
754}
755
756impl SchemaPayload {
757    /// CBOR-encode this prepared message for embedding in RequestCall/RequestResponse.
758    pub fn to_cbor(&self) -> CborPayload {
759        CborPayload(facet_cbor::to_vec(self).expect("schema CBOR serialization should not fail"))
760    }
761
762    /// Parse a CBOR-encoded schema message from bytes.
763    pub fn from_cbor(bytes: &[u8]) -> Result<SchemaPayload, facet_cbor::CborError> {
764        facet_cbor::from_slice(bytes)
765    }
766}
767
768/// Transparent wrapper around borrowed bytes that are already postcard-encoded.
769/// Used as a sentinel type for passthrough detection in serializers.
770#[repr(transparent)]
771pub struct RawPostcardBorrowed<'a>(pub &'a [u8]);
772
773/// Sentinel shape for borrowed passthrough bytes. Serializers check against
774/// this to distinguish pre-encoded bytes from regular `&[u8]`/`Vec<u8>` values.
775pub static RAW_POSTCARD_BORROWED_SHAPE: Shape =
776    Shape::builder_for_sized::<RawPostcardBorrowed<'static>>("RawPostcardBorrowed").build();
777
778/// Create an `OpaqueSerialize` for already-encoded postcard bytes.
779/// The serializer detects the sentinel shape and writes bytes directly (passthrough).
780pub fn opaque_encoded_borrowed(bytes: &&[u8]) -> OpaqueSerialize {
781    OpaqueSerialize {
782        ptr: PtrConst::new((bytes as *const &[u8]).cast::<RawPostcardBorrowed<'_>>()),
783        shape: &RAW_POSTCARD_BORROWED_SHAPE,
784    }
785}