Skip to main content

chaincodec_core/
schema.rs

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