Skip to main content

shape/
macros.rs

1//! `shape!`: a spec written as the example it is, in the style of
2//! `serde_json::json!`. Objects in braces, arrays in brackets, the type
3//! tokens bare (`String`, `Number`, `Boolean`, `Object`, `Array`,
4//! `Function`, `Integer`, `Date`, `Any`), and any other Rust expression a
5//! spec can be made from: a literal, a builder call, a node.
6//!
7//! ```
8//! use shape::{min, shape, Token};
9//! let s = shape::Schema::new(shape!({
10//!     "name": String,
11//!     "port": 8080,
12//!     "tags": [String],
13//!     "age": min(0, Token::Integer),
14//!     "addr": { "zip": String },
15//! }));
16//! assert!(s.valid(&shape::Value::from(serde_json::json!({ "name": "x", "age": 3, "addr": { "zip": "1" } }))));
17//! ```
18
19/// A spec written as the example it is; see the module documentation.
20#[macro_export]
21macro_rules! shape {
22    ({ $($body:tt)* }) => { $crate::__shape_obj!([] $($body)*) };
23    ([ $($body:tt)* ]) => { $crate::__shape_arr!([] $($body)*) };
24    ($t:ident) => { $crate::__shape_token!($t) };
25    ($e:expr) => { $crate::Spec::from($e) };
26}
27
28#[doc(hidden)]
29#[macro_export]
30macro_rules! __shape_token {
31    (String) => {
32        $crate::Spec::from($crate::Token::String)
33    };
34    (Number) => {
35        $crate::Spec::from($crate::Token::Number)
36    };
37    (Boolean) => {
38        $crate::Spec::from($crate::Token::Boolean)
39    };
40    (Object) => {
41        $crate::Spec::from($crate::Token::Object)
42    };
43    (Array) => {
44        $crate::Spec::from($crate::Token::Array)
45    };
46    (Function) => {
47        $crate::Spec::from($crate::Token::Function)
48    };
49    (Integer) => {
50        $crate::Spec::from($crate::Token::Integer)
51    };
52    (Date) => {
53        $crate::Spec::from($crate::Token::Date)
54    };
55    (BigInt) => {
56        $crate::Spec::from($crate::Token::BigInt)
57    };
58    (Any) => {
59        $crate::Spec::from($crate::Token::Any)
60    };
61    ($other:ident) => {
62        $crate::Spec::from($other)
63    };
64}
65
66#[doc(hidden)]
67#[macro_export]
68macro_rules! __shape_key {
69    ($k:literal) => {
70        $k.to_string()
71    };
72    ($k:ident) => {
73        stringify!($k).to_string()
74    };
75}
76
77#[doc(hidden)]
78#[macro_export]
79macro_rules! __shape_obj {
80    ([$($acc:tt)*]) => { $crate::Spec::Obj(vec![$($acc)*]) };
81    ([$($acc:tt)*] , $($rest:tt)*) => { $crate::__shape_obj!([$($acc)*] $($rest)*) };
82    ([$($acc:tt)*] $k:literal : { $($v:tt)* } $($rest:tt)*) => {
83        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::shape!({ $($v)* })),] $($rest)*)
84    };
85    ([$($acc:tt)*] $k:ident : { $($v:tt)* } $($rest:tt)*) => {
86        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::shape!({ $($v)* })),] $($rest)*)
87    };
88    ([$($acc:tt)*] $k:literal : [ $($v:tt)* ] $($rest:tt)*) => {
89        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::shape!([ $($v)* ])),] $($rest)*)
90    };
91    ([$($acc:tt)*] $k:ident : [ $($v:tt)* ] $($rest:tt)*) => {
92        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::shape!([ $($v)* ])),] $($rest)*)
93    };
94    ([$($acc:tt)*] $k:literal : $v:ident , $($rest:tt)*) => {
95        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::__shape_token!($v)),] $($rest)*)
96    };
97    ([$($acc:tt)*] $k:literal : $v:ident) => {
98        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::__shape_token!($v)),])
99    };
100    ([$($acc:tt)*] $k:ident : $v:ident , $($rest:tt)*) => {
101        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::__shape_token!($v)),] $($rest)*)
102    };
103    ([$($acc:tt)*] $k:ident : $v:ident) => {
104        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::__shape_token!($v)),])
105    };
106    ([$($acc:tt)*] $k:literal : $v:expr , $($rest:tt)*) => {
107        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::Spec::from($v)),] $($rest)*)
108    };
109    ([$($acc:tt)*] $k:literal : $v:expr) => {
110        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::Spec::from($v)),])
111    };
112    ([$($acc:tt)*] $k:ident : $v:expr , $($rest:tt)*) => {
113        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::Spec::from($v)),] $($rest)*)
114    };
115    ([$($acc:tt)*] $k:ident : $v:expr) => {
116        $crate::__shape_obj!([$($acc)* ($crate::__shape_key!($k), $crate::Spec::from($v)),])
117    };
118}
119
120#[doc(hidden)]
121#[macro_export]
122macro_rules! __shape_arr {
123    ([$($acc:tt)*]) => { $crate::Spec::Arr(vec![$($acc)*]) };
124    ([$($acc:tt)*] , $($rest:tt)*) => { $crate::__shape_arr!([$($acc)*] $($rest)*) };
125    ([$($acc:tt)*] { $($v:tt)* } $($rest:tt)*) => {
126        $crate::__shape_arr!([$($acc)* $crate::shape!({ $($v)* }),] $($rest)*)
127    };
128    ([$($acc:tt)*] [ $($v:tt)* ] $($rest:tt)*) => {
129        $crate::__shape_arr!([$($acc)* $crate::shape!([ $($v)* ]),] $($rest)*)
130    };
131    ([$($acc:tt)*] $v:ident , $($rest:tt)*) => {
132        $crate::__shape_arr!([$($acc)* $crate::__shape_token!($v),] $($rest)*)
133    };
134    ([$($acc:tt)*] $v:ident) => {
135        $crate::__shape_arr!([$($acc)* $crate::__shape_token!($v),])
136    };
137    ([$($acc:tt)*] $v:expr , $($rest:tt)*) => {
138        $crate::__shape_arr!([$($acc)* $crate::Spec::from($v),] $($rest)*)
139    };
140    ([$($acc:tt)*] $v:expr) => {
141        $crate::__shape_arr!([$($acc)* $crate::Spec::from($v),])
142    };
143}
144
145#[cfg(test)]
146mod tests {
147    use crate::builders::min;
148    use crate::{Schema, Spec, Token, Value};
149
150    fn j(s: &str) -> Value {
151        Value::from(serde_json::from_str::<serde_json::Value>(s).unwrap())
152    }
153
154    #[test]
155    fn writes_specs_as_examples() {
156        let spec = shape!({
157            "name": String,
158            port: 8080,
159            "tags": [String],
160            "pair": [Number, "x"],
161            "age": min(0, Token::Integer),
162            "addr": { "zip": String, "lines": [{ "text": String }] },
163            "empty": {},
164            "anything": Any,
165        });
166        let s = Schema::new(spec);
167        let out = s
168            .validate(j(
169                r#"{"name":"x","pair":[1],"age":3,"addr":{"zip":"1","lines":[{"text":"t"}]},"empty":{"k":1}}"#,
170            ))
171            .unwrap();
172        assert_eq!(
173            serde_json::Value::from(out),
174            serde_json::json!({"name":"x","port":8080,"tags":[],"pair":[1,"x"],"age":3,"addr":{"zip":"1","lines":[{"text":"t"}]},"empty":{"k":1}})
175        );
176        assert!(matches!(shape!(String), Spec::Token(Token::String)));
177        assert!(matches!(shape!(Number), Spec::Token(Token::Number)));
178        assert!(matches!(shape!(Boolean), Spec::Token(Token::Boolean)));
179        assert!(matches!(shape!(Object), Spec::Token(Token::Object)));
180        assert!(matches!(shape!(Array), Spec::Token(Token::Array)));
181        assert!(matches!(shape!(Function), Spec::Token(Token::Function)));
182        assert!(matches!(shape!(Integer), Spec::Token(Token::Integer)));
183        assert!(matches!(shape!(Date), Spec::Token(Token::Date)));
184        assert!(matches!(shape!(BigInt), Spec::Token(Token::BigInt)));
185        assert!(matches!(shape!(Any), Spec::Token(Token::Any)));
186        let node = min(1, Token::Number);
187        assert!(matches!(shape!(node), Spec::Node(_)));
188        assert!(matches!(shape!(5), Spec::Value(Value::Num(_))));
189        assert!(matches!(shape!([]), Spec::Arr(_)));
190        assert!(matches!(shape!([1,]), Spec::Arr(_)));
191        assert!(matches!(shape!({ a: 1, }), Spec::Obj(_)));
192        assert!(matches!(
193            shape!({ "a": [Number], b: { c: String } }),
194            Spec::Obj(_)
195        ));
196    }
197}