cw_schema/
lib.rs

1//! CosmWasm is a smart contract platform for the Cosmos ecosystem.
2//! This crate is a dependency for CosmWasm contracts to generate schema files for their messages.
3//!
4//! For more information, see: <https://cosmwasm.cosmos.network>
5
6#![no_std]
7
8extern crate alloc;
9
10#[cfg(feature = "std")]
11extern crate std;
12
13use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
14use core::{any::TypeId, hash::BuildHasherDefault};
15use indexmap::IndexMap;
16use serde::{Deserialize, Serialize};
17use serde_with::skip_serializing_none;
18use siphasher::sip::SipHasher;
19
20pub use cw_schema_derive::Schemaifier;
21
22pub type DefinitionReference = usize;
23
24mod default_impls;
25
26#[skip_serializing_none]
27#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
28#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
29#[serde(rename_all = "camelCase")]
30pub struct StructProperty {
31    #[serde(default, skip_serializing_if = "core::ops::Not::not")]
32    pub defaulting: bool,
33    pub description: Option<Cow<'static, str>>,
34    pub value: DefinitionReference,
35}
36
37#[skip_serializing_none]
38#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
39#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
40#[serde(rename_all = "camelCase", untagged)]
41pub enum StructType {
42    Unit,
43    Named {
44        properties: BTreeMap<Cow<'static, str>, StructProperty>,
45    },
46    Tuple {
47        items: Vec<DefinitionReference>,
48    },
49}
50
51#[skip_serializing_none]
52#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
53#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
54#[serde(rename_all = "camelCase")]
55pub struct EnumCase {
56    pub description: Option<Cow<'static, str>>,
57    #[serde(flatten)]
58    pub value: EnumValue,
59}
60
61#[skip_serializing_none]
62#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
63#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
64#[serde(rename_all = "camelCase", tag = "type")]
65pub enum EnumValue {
66    Unit,
67    Named {
68        properties: BTreeMap<Cow<'static, str>, StructProperty>,
69    },
70    Tuple {
71        items: Vec<DefinitionReference>,
72    },
73}
74
75#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
76#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
77#[serde(rename_all = "camelCase")]
78#[non_exhaustive]
79pub enum MapKind {
80    BTree,
81    Hash,
82}
83
84#[skip_serializing_none]
85#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
86#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
87#[serde(rename_all = "camelCase", tag = "type")]
88pub enum NodeType {
89    // Floating point numbers
90    Float,
91    Double,
92
93    // Decimal numbers
94    Decimal {
95        precision: u64,
96        signed: bool,
97    },
98
99    // Integer numbers
100    Integer {
101        precision: u64,
102        signed: bool,
103    },
104
105    Address,
106    Binary,
107    Checksum,
108    HexBinary,
109    Timestamp,
110
111    String,
112    Boolean,
113    Array {
114        items: DefinitionReference,
115    },
116    Struct(StructType),
117    Tuple {
118        items: Vec<DefinitionReference>,
119    },
120    Enum {
121        discriminator: Option<Cow<'static, str>>,
122        cases: BTreeMap<Cow<'static, str>, EnumCase>,
123    },
124
125    Map {
126        kind: MapKind,
127        key: DefinitionReference,
128        value: DefinitionReference,
129    },
130
131    Boxed {
132        inner: DefinitionReference,
133    },
134    Optional {
135        inner: DefinitionReference,
136    },
137    Unit,
138}
139
140#[skip_serializing_none]
141#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
142#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
143#[serde(rename_all = "camelCase")]
144pub struct Node {
145    pub name: Cow<'static, str>,
146    pub description: Option<Cow<'static, str>>,
147    #[serde(flatten)]
148    pub value: NodeType,
149}
150
151#[skip_serializing_none]
152#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
153#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
154#[serde(rename_all = "camelCase")]
155pub struct SchemaV1 {
156    pub root: DefinitionReference,
157    pub definitions: Vec<Node>,
158}
159
160#[skip_serializing_none]
161#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
162#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
163#[serde(rename_all = "camelCase", tag = "type")]
164#[non_exhaustive]
165pub enum Schema {
166    V1(SchemaV1),
167}
168
169#[derive(Hash, PartialEq, Eq)]
170pub struct Identifier(TypeId);
171
172impl Identifier {
173    pub fn of<T>() -> Self
174    where
175        T: ?Sized,
176    {
177        Identifier(typeid::of::<T>())
178    }
179}
180
181enum NodeSpot {
182    Reserved,
183    Occupied(Node),
184}
185
186#[derive(Default)]
187pub struct SchemaVisitor {
188    schemas: IndexMap<Identifier, NodeSpot, BuildHasherDefault<SipHasher>>,
189}
190
191impl SchemaVisitor {
192    pub fn get_reference<T: Schemaifier>(&self) -> Option<DefinitionReference> {
193        self.schemas.get_index_of(&T::id())
194    }
195
196    pub fn get_schema<T: Schemaifier>(&self) -> Option<&Node> {
197        self.schemas
198            .get(&T::id())
199            .and_then(|node_spot| match node_spot {
200                NodeSpot::Occupied(node) => Some(node),
201                NodeSpot::Reserved => None,
202            })
203    }
204
205    pub fn insert(&mut self, id: Identifier, node: Node) -> DefinitionReference {
206        let (id, _) = self.schemas.insert_full(id, NodeSpot::Occupied(node));
207        id
208    }
209
210    pub fn reserve_spot(&mut self, id: Identifier) -> DefinitionReference {
211        let (id, _) = self.schemas.insert_full(id, NodeSpot::Reserved);
212        id
213    }
214
215    /// Transform this visitor into a vector where the `DefinitionReference` can be used as an index
216    /// to access the schema of the particular node.
217    pub fn into_vec(self) -> Vec<Node> {
218        self.schemas
219            .into_values()
220            .map(|node_spot| {
221                if let NodeSpot::Occupied(node) = node_spot {
222                    node
223                } else {
224                    panic!("reserved and never filled spot");
225                }
226            })
227            .collect()
228    }
229}
230
231pub trait Schemaifier {
232    fn id() -> Identifier {
233        Identifier::of::<Self>()
234    }
235
236    fn visit_schema(visitor: &mut SchemaVisitor) -> DefinitionReference;
237}
238
239pub fn schema_of<T: Schemaifier + ?Sized>() -> Schema {
240    let mut visitor = SchemaVisitor::default();
241    Schema::V1(SchemaV1 {
242        root: T::visit_schema(&mut visitor),
243        definitions: visitor.into_vec(),
244    })
245}
246
247#[doc(hidden)]
248pub mod reexport {
249    pub use alloc::collections::BTreeMap;
250}