async_graphql_value/
macros.rs

1/// Construct a `ConstValue`.
2#[macro_export]
3macro_rules! value {
4    ($($json:tt)+) => {
5        $crate::value_internal!($($json)+)
6    };
7}
8
9#[macro_export]
10#[doc(hidden)]
11macro_rules! value_internal {
12    // Done with trailing comma.
13    (@array [$($elems:expr,)*]) => {
14        $crate::value_internal_vec![$($elems,)*]
15    };
16
17    // Done without trailing comma.
18    (@array [$($elems:expr),*]) => {
19        $crate::value_internal_vec![$($elems),*]
20    };
21
22    // Next element is `null`.
23    (@array [$($elems:expr,)*] null $($rest:tt)*) => {
24        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(null)] $($rest)*)
25    };
26
27    // Next element is `true`.
28    (@array [$($elems:expr,)*] true $($rest:tt)*) => {
29        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(true)] $($rest)*)
30    };
31
32    // Next element is `false`.
33    (@array [$($elems:expr,)*] false $($rest:tt)*) => {
34        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(false)] $($rest)*)
35    };
36
37    // Next element is an array.
38    (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
39        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!([$($array)*])] $($rest)*)
40    };
41
42    // Next element is a map.
43    (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
44        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!({$($map)*})] $($rest)*)
45    };
46
47    // Next element is an expression followed by comma.
48    (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
49        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!($next),] $($rest)*)
50    };
51
52    // Last element is an expression with no trailing comma.
53    (@array [$($elems:expr,)*] $last:expr) => {
54        $crate::value_internal!(@array [$($elems,)* $crate::value_internal!($last)])
55    };
56
57    // Comma after the most recent element.
58    (@array [$($elems:expr),*] , $($rest:tt)*) => {
59        $crate::value_internal!(@array [$($elems,)*] $($rest)*)
60    };
61
62    // Unexpected token after most recent element.
63    (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
64        $crate::value_unexpected!($unexpected)
65    };
66
67    // Done.
68    (@object $object:ident () () ()) => {};
69
70    // Insert the current entry followed by trailing comma.
71    (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
72        let _ = $object.insert($crate::Name::new($($key)+), $value);
73        $crate::value_internal!(@object $object () ($($rest)*) ($($rest)*));
74    };
75
76    // Current entry followed by unexpected token.
77    (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
78        $crate::value_unexpected!($unexpected);
79    };
80
81    // Insert the last entry without trailing comma.
82    (@object $object:ident [$($key:tt)+] ($value:expr)) => {
83        let _ = $object.insert($crate::Name::new($($key)+), $value);
84    };
85
86    // Next value is `null`.
87    (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
88        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(null)) $($rest)*);
89    };
90
91    // Next value is `true`.
92    (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
93        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(true)) $($rest)*);
94    };
95
96    // Next value is `false`.
97    (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
98        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(false)) $($rest)*);
99    };
100
101    // Next value is an array.
102    (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
103        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!([$($array)*])) $($rest)*);
104    };
105
106    // Next value is a map.
107    (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
108        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!({$($map)*})) $($rest)*);
109    };
110
111    // Next value is an expression followed by comma.
112    (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
113        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!($value)) , $($rest)*);
114    };
115
116    // Last value is an expression with no trailing comma.
117    (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
118        $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!($value)));
119    };
120
121    // Missing value for last entry. Trigger a reasonable error message.
122    (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
123        // "unexpected end of macro invocation"
124        $crate::value_internal!();
125    };
126
127    // Missing colon and value for last entry. Trigger a reasonable error
128    // message.
129    (@object $object:ident ($($key:tt)+) () $copy:tt) => {
130        // "unexpected end of macro invocation"
131        $crate::value_internal!();
132    };
133
134    // Misplaced colon. Trigger a reasonable error message.
135    (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
136        // Takes no arguments so "no rules expected the token `:`".
137        $crate::value_unexpected!($colon);
138    };
139
140    // Found a comma inside a key. Trigger a reasonable error message.
141    (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
142        // Takes no arguments so "no rules expected the token `,`".
143        $crate::value_unexpected!($comma);
144    };
145
146    // Key is fully parenthesized. This avoids clippy double_parens false
147    // positives because the parenthesization may be necessary here.
148    (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
149        $crate::value_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
150    };
151
152    // Refuse to absorb colon token into key expression.
153    (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
154        $crate::value_expect_expr_comma!($($unexpected)+);
155    };
156
157    // Munch a token into the current key.
158    (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
159        $crate::value_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
160    };
161
162    //////////////////////////////////////////////////////////////////////////
163    // The main implementation.
164    //
165    // Must be invoked as: value_internal!($($json)+)
166    //////////////////////////////////////////////////////////////////////////
167
168    (null) => {
169        $crate::ConstValue::Null
170    };
171
172    (true) => {
173        $crate::ConstValue::Boolean(true)
174    };
175
176    (false) => {
177        $crate::ConstValue::Boolean(false)
178    };
179
180    ([]) => {
181        $crate::ConstValue::List($crate::value_internal_vec![])
182    };
183
184    ([ $($tt:tt)+ ]) => {
185        $crate::ConstValue::List($crate::value_internal!(@array [] $($tt)+))
186    };
187
188    ({}) => {
189        $crate::ConstValue::Object(Default::default())
190    };
191
192    ({ $($tt:tt)+ }) => {
193        $crate::ConstValue::Object({
194            let mut object = $crate::indexmap::IndexMap::new();
195            $crate::value_internal!(@object object () ($($tt)+) ($($tt)+));
196            object
197        })
198    };
199
200    // Any Serialize type: numbers, strings, struct literals, variables etc.
201    // Must be below every other rule.
202    ($other:expr) => {
203        $crate::to_value(&$other).unwrap()
204    };
205}
206
207#[macro_export]
208#[doc(hidden)]
209macro_rules! value_internal_vec {
210    ($($content:tt)*) => {
211        vec![$($content)*]
212    };
213}
214
215#[macro_export]
216#[doc(hidden)]
217macro_rules! value_unexpected {
218    () => {};
219}
220
221#[macro_export]
222#[doc(hidden)]
223macro_rules! value_expect_expr_comma {
224    ($e:expr , $($tt:tt)*) => {};
225}
226
227#[cfg(test)]
228mod tests {
229    use indexmap::IndexMap;
230
231    use crate::{ConstValue, Name};
232
233    #[test]
234    fn test_macro() {
235        assert_eq!(value!(1), ConstValue::Number(1.into()));
236        assert_eq!(value!(1 + 2), ConstValue::Number(3.into()));
237        assert_eq!(value!("abc"), ConstValue::String("abc".into()));
238        assert_eq!(value!(true), ConstValue::Boolean(true));
239        assert_eq!(
240            value!([1, 2, 3]),
241            ConstValue::List((1..=3).map(|n| ConstValue::Number(n.into())).collect())
242        );
243        assert_eq!(
244            value!([1, 2, 3,]),
245            ConstValue::List((1..=3).map(|n| ConstValue::Number(n.into())).collect())
246        );
247        assert_eq!(value!({"a": 10, "b": true}), {
248            let mut map = IndexMap::new();
249            map.insert(Name::new("a"), ConstValue::Number(10.into()));
250            map.insert(Name::new("b"), ConstValue::Boolean(true));
251            ConstValue::Object(map)
252        });
253    }
254}