Skip to main content

chaincodec_core/
schema.rs

1//! Schema types — the in-memory representation of a parsed CSDL schema.
2
3use crate::chain::ChainId;
4use crate::event::EventFingerprint;
5use crate::types::CanonicalType;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Trust level assigned to a schema in the registry.
10#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum TrustLevel {
13    #[default]
14    Unverified,
15    CommunityVerified,
16    MaintainerVerified,
17    ProtocolVerified,
18}
19
20impl std::fmt::Display for TrustLevel {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        let s = match self {
23            TrustLevel::Unverified => "unverified",
24            TrustLevel::CommunityVerified => "community_verified",
25            TrustLevel::MaintainerVerified => "maintainer_verified",
26            TrustLevel::ProtocolVerified => "protocol_verified",
27        };
28        write!(f, "{s}")
29    }
30}
31
32/// Definition of a single field within a schema.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct FieldDef {
35    /// ChainCodec canonical type
36    pub ty: CanonicalType,
37    /// EVM: is this an indexed topic?
38    pub indexed: bool,
39    /// Whether this field can be absent / null
40    pub nullable: bool,
41    /// Optional human-readable description
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub description: Option<String>,
44}
45
46/// Metadata block attached to a schema.
47#[derive(Debug, Clone, Default, Serialize, Deserialize)]
48pub struct SchemaMeta {
49    /// Protocol slug, e.g. "uniswap-v3"
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub protocol: Option<String>,
52    /// Category, e.g. "dex", "lending", "bridge", "nft"
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub category: Option<String>,
55    /// Whether maintainers have reviewed and verified this schema
56    pub verified: bool,
57    /// Assigned trust level
58    pub trust_level: TrustLevel,
59    /// Optional protocol team signature (hex-encoded)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub provenance_sig: Option<String>,
62}
63
64/// A parsed, validated schema definition.
65/// This is the in-memory representation of a CSDL file.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Schema {
68    /// PascalCase schema name, e.g. "UniswapV3Swap"
69    pub name: String,
70    /// Schema version (increments on breaking changes)
71    pub version: u32,
72    /// Chains this schema applies to (by slug)
73    pub chains: Vec<String>,
74    /// Contract address(es) — None means "any address"
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub address: Option<Vec<String>>,
77    /// The blockchain event name, e.g. "Swap"
78    pub event: String,
79    /// Fingerprint: keccak256 of the event signature (EVM) or SHA-256 (non-EVM)
80    pub fingerprint: EventFingerprint,
81    /// Optional: the schema this one supersedes
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub supersedes: Option<String>,
84    /// Optional: forward pointer to a successor schema
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub superseded_by: Option<String>,
87    /// Whether this schema is deprecated
88    pub deprecated: bool,
89    /// Ordered field definitions (order matters for ABI decode)
90    pub fields: Vec<(String, FieldDef)>,
91    /// Metadata
92    pub meta: SchemaMeta,
93}
94
95impl Schema {
96    /// Returns field definitions as a lookup map (name → def).
97    pub fn fields_map(&self) -> HashMap<&str, &FieldDef> {
98        self.fields.iter().map(|(k, v)| (k.as_str(), v)).collect()
99    }
100
101    /// Returns only the indexed fields (EVM topics[1..]).
102    pub fn indexed_fields(&self) -> Vec<(&str, &FieldDef)> {
103        self.fields
104            .iter()
105            .filter(|(_, f)| f.indexed)
106            .map(|(k, v)| (k.as_str(), v))
107            .collect()
108    }
109
110    /// Returns only the non-indexed fields (EVM data payload).
111    pub fn data_fields(&self) -> Vec<(&str, &FieldDef)> {
112        self.fields
113            .iter()
114            .filter(|(_, f)| !f.indexed)
115            .map(|(k, v)| (k.as_str(), v))
116            .collect()
117    }
118}
119
120/// A thread-safe, read-only view of a schema registry.
121/// Concrete implementations live in `chaincodec-registry`.
122pub trait SchemaRegistry: Send + Sync {
123    /// Look up a schema by its fingerprint.
124    fn get_by_fingerprint(&self, fp: &EventFingerprint) -> Option<Schema>;
125
126    /// Look up a schema by name and optional version.
127    /// If `version` is None, returns the latest non-deprecated version.
128    fn get_by_name(&self, name: &str, version: Option<u32>) -> Option<Schema>;
129
130    /// Returns all schemas applicable to the given chain slug.
131    fn list_for_chain(&self, chain_slug: &str) -> Vec<Schema>;
132
133    /// Returns the full evolution chain for a schema: from oldest to newest.
134    fn history(&self, name: &str) -> Vec<Schema>;
135}