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