cw_schema/
lib.rs

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    // Floating point numbers
88    Float,
89    Double,
90
91    // Decimal numbers
92    Decimal {
93        precision: u64,
94        signed: bool,
95    },
96
97    // Integer numbers
98    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    /// Transform this visitor into a vector where the `DefinitionReference` can be used as an index
214    /// to access the schema of the particular node.
215    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}