1#![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 Float,
91 Double,
92
93 Decimal {
95 precision: u64,
96 signed: bool,
97 },
98
99 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 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}