Skip to main content

cc_lb_plugin_wire/
schema.rs

1//! Wire versioning contract shared between host and plugin authors.
2//!
3//! The host and each plugin agree on a `WireVersion` per hook. Plugin
4//! authors declare their hook's version in `#[hook(...)]` metadata, and
5//! the host verifies at admission time that the declared version is in
6//! the host's supported list AND that the wasm's embedded layout
7//! fingerprint matches the host's computed fingerprint.
8
9use core::fmt;
10
11/// Wire schema version. Distinct enum values indicate incompatible
12/// struct layouts. Bump when adding/removing/reordering fields of ANY
13/// hook's wire type. Once V2 is added, V1 stays as a supported legacy
14/// version until deprecated by explicit host-list removal.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16#[repr(u8)]
17pub enum WireVersion {
18    V1 = 1,
19}
20
21impl WireVersion {
22    pub const fn as_u8(self) -> u8 {
23        self as u8
24    }
25
26    pub const fn from_u8(v: u8) -> Option<Self> {
27        match v {
28            1 => Some(Self::V1),
29            _ => None,
30        }
31    }
32
33    pub const fn as_str(self) -> &'static str {
34        match self {
35            Self::V1 => "v1",
36        }
37    }
38}
39
40impl fmt::Display for WireVersion {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(self.as_str())
43    }
44}
45
46/// Plugin hook kind. Each hook is a distinct wire contract.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
48pub enum HookKind {
49    Filter,
50    Shape,
51    Observe,
52    TransformResponse,
53    TransformSseEvent,
54}
55
56impl HookKind {
57    pub const fn as_str(self) -> &'static str {
58        match self {
59            Self::Filter => "filter",
60            Self::Shape => "shape",
61            Self::Observe => "observe",
62            Self::TransformResponse => "transform_response",
63            Self::TransformSseEvent => "transform_sse_event",
64        }
65    }
66
67    /// Wasm function export name for this hook (guest-side).
68    pub const fn export_name(self) -> &'static str {
69        match self {
70            Self::Filter => "cc_lb_filter",
71            Self::Shape => "cc_lb_shape",
72            Self::Observe => "cc_lb_observe",
73            Self::TransformResponse => "cc_lb_transform_response",
74            Self::TransformSseEvent => "cc_lb_transform_sse_event",
75        }
76    }
77
78    /// Custom section name prefix. The full section name for a specific
79    /// version is `{prefix}.{version_str}` (e.g., `cc_lb.schema.filter.v1`).
80    pub const fn section_prefix(self) -> &'static str {
81        match self {
82            Self::Filter => "cc_lb.schema.filter",
83            Self::Shape => "cc_lb.schema.shape",
84            Self::Observe => "cc_lb.schema.observe",
85            Self::TransformResponse => "cc_lb.schema.transform_response",
86            Self::TransformSseEvent => "cc_lb.schema.transform_sse_event",
87        }
88    }
89
90    /// All hook kinds — for iteration.
91    pub const ALL: &'static [HookKind] = &[
92        Self::Filter,
93        Self::Shape,
94        Self::Observe,
95        Self::TransformResponse,
96        Self::TransformSseEvent,
97    ];
98
99    pub fn parse(s: &str) -> Option<Self> {
100        match s {
101            "filter" => Some(Self::Filter),
102            "shape" => Some(Self::Shape),
103            "observe" => Some(Self::Observe),
104            "transform_response" => Some(Self::TransformResponse),
105            "transform_sse_event" => Some(Self::TransformSseEvent),
106            _ => None,
107        }
108    }
109}
110
111impl fmt::Display for HookKind {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        f.write_str(self.as_str())
114    }
115}
116
117/// Host-supported versions per hook. Update when adding new versions.
118pub const HOST_SUPPORTED_FILTER_VERSIONS: &[WireVersion] = &[WireVersion::V1];
119pub const HOST_SUPPORTED_SHAPE_VERSIONS: &[WireVersion] = &[WireVersion::V1];
120pub const HOST_SUPPORTED_OBSERVE_VERSIONS: &[WireVersion] = &[WireVersion::V1];
121pub const HOST_SUPPORTED_TRANSFORM_RESPONSE_VERSIONS: &[WireVersion] = &[WireVersion::V1];
122pub const HOST_SUPPORTED_TRANSFORM_SSE_EVENT_VERSIONS: &[WireVersion] = &[WireVersion::V1];
123
124pub fn host_supported_versions(hook: HookKind) -> &'static [WireVersion] {
125    match hook {
126        HookKind::Filter => HOST_SUPPORTED_FILTER_VERSIONS,
127        HookKind::Shape => HOST_SUPPORTED_SHAPE_VERSIONS,
128        HookKind::Observe => HOST_SUPPORTED_OBSERVE_VERSIONS,
129        HookKind::TransformResponse => HOST_SUPPORTED_TRANSFORM_RESPONSE_VERSIONS,
130        HookKind::TransformSseEvent => HOST_SUPPORTED_TRANSFORM_SSE_EVENT_VERSIONS,
131    }
132}
133
134pub fn host_supports(hook: HookKind, version: WireVersion) -> bool {
135    host_supported_versions(hook).contains(&version)
136}
137
138/// Layout fingerprint published by each wire struct via the
139/// `#[derive(WireSchema)]` proc-macro (defined in
140/// `cc-lb-pdk-wasmtime-macros`). The macro walks the struct AST,
141/// serialises fields to a canonical descriptor string, then embeds
142/// `BLAKE3(descriptor)` as a 32-byte const.
143pub trait WireSchema {
144    const FINGERPRINT: [u8; 32];
145    const DESCRIPTOR: &'static str;
146}
147
148/// Packed `(out_ptr, out_len)` return value used by every guest hook export.
149pub const fn pack_ret(ptr: u32, len: u32) -> u64 {
150    ((ptr as u64) << 32) | (len as u64)
151}
152
153/// Inverse of [`pack_ret`].
154pub const fn unpack_ret(packed: u64) -> (u32, u32) {
155    let ptr = (packed >> 32) as u32;
156    let len = (packed & 0xFFFF_FFFF) as u32;
157    (ptr, len)
158}