juniper/macros/
graphql_value.rs

1//! [`graphql_value!`] macro implementation.
2//!
3//! [`graphql_value!`]: graphql_value
4
5/// Constructs [`Value`]s via JSON-like syntax.
6///
7/// [`Value`] objects are used mostly when creating custom errors from fields.
8///
9/// [`Value::Object`] key should implement [`AsRef`]`<`[`str`]`>`.
10/// ```rust
11/// # use juniper::{graphql_value, Value};
12/// #
13/// let code = 200;
14/// let features = ["key", "value"];
15///
16/// let value: Value = graphql_value!({
17///     "code": code,
18///     "success": code == 200,
19///     "payload": {
20///         features[0]: features[1],
21///     },
22/// });
23/// ```
24///
25/// # Example
26///
27/// Resulting JSON will look just like what you passed in.
28/// ```rust
29/// # use juniper::{graphql_value, DefaultScalarValue, Value};
30/// #
31/// # type V = Value<DefaultScalarValue>;
32/// #
33/// # let _: V =
34/// graphql_value!(null);
35/// # let _: V =
36/// graphql_value!(1234);
37/// # let _: V =
38/// graphql_value!("test");
39/// # let _: V =
40/// graphql_value!([1234, "test", true]);
41/// # let _: V =
42/// graphql_value!({"key": "value", "foo": 1234});
43/// ```
44///
45/// [`Value`]: crate::Value
46/// [`Value::Object`]: crate::Value::Object
47#[macro_export]
48macro_rules! graphql_value {
49    ///////////
50    // Array //
51    ///////////
52
53    // Done with trailing comma.
54    (@array [$($elems:expr,)*]) => {
55        $crate::Value::list(vec![
56            $( $elems, )*
57        ])
58    };
59
60    // Done without trailing comma.
61    (@array [$($elems:expr),*]) => {
62        $crate::Value::list(vec![
63            $( $crate::graphql_value!($elems), )*
64        ])
65    };
66
67    // Next element is `null`.
68    (@array [$($elems:expr,)*] null $($rest:tt)*) => {
69        $crate::graphql_value!(
70            @array [$($elems,)* $crate::graphql_value!(null)] $($rest)*
71        )
72    };
73
74    // Next element is `None`.
75    (@array [$($elems:expr,)*] None $($rest:tt)*) => {
76        $crate::graphql_value!(
77            @array [$($elems,)* $crate::graphql_value!(None)] $($rest)*
78        )
79    };
80
81    // Next element is an array.
82    (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
83        $crate::graphql_value!(
84            @array [$($elems,)* $crate::graphql_value!([$($array)*])] $($rest)*
85        )
86    };
87
88    // Next element is a map.
89    (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
90        $crate::graphql_value!(
91            @array [$($elems,)* $crate::graphql_value!({$($map)*})] $($rest)*
92        )
93    };
94
95    // Next element is an expression followed by comma.
96    (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
97        $crate::graphql_value!(
98            @array [$($elems,)* $crate::graphql_value!($next),] $($rest)*
99        )
100    };
101
102    // Last element is an expression with no trailing comma.
103    (@array [$($elems:expr,)*] $last:expr) => {
104        $crate::graphql_value!(
105            @array [$($elems,)* $crate::graphql_value!($last)]
106        )
107    };
108
109    // Comma after the most recent element.
110    (@array [$($elems:expr),*] , $($rest:tt)*) => {
111        $crate::graphql_value!(@array [$($elems,)*] $($rest)*)
112    };
113
114    // Unexpected token after most recent element.
115    (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
116        $crate::graphql_value!(@unexpected $unexpected)
117    };
118
119    ////////////
120    // Object //
121    ////////////
122
123    // Done.
124    (@object $object:ident () () ()) => {};
125
126    // Insert the current entry followed by trailing comma.
127    (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
128        let _ = $object.add_field(($($key)+), $value);
129        $crate::graphql_value!(@object $object () ($($rest)*) ($($rest)*));
130    };
131
132    // Current entry followed by unexpected token.
133    (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
134        $crate::graphql_value!(@unexpected $unexpected);
135    };
136
137    // Insert the last entry without trailing comma.
138    (@object $object:ident [$($key:tt)+] ($value:expr)) => {
139        let _ = $object.add_field(($($key)+), $value);
140    };
141
142    // Next value is `null`.
143    (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
144        $crate::graphql_value!(
145            @object $object
146            [$($key)+]
147            ($crate::graphql_value!(null)) $($rest)*
148        );
149    };
150
151    // Next value is `None`.
152    (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => {
153        $crate::graphql_value!(
154            @object $object
155            [$($key)+]
156            ($crate::graphql_value!(None)) $($rest)*
157        );
158    };
159
160    // Next value is an array.
161    (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
162        $crate::graphql_value!(
163            @object $object
164            [$($key)+]
165            ($crate::graphql_value!([$($array)*])) $($rest)*
166        );
167    };
168
169    // Next value is a map.
170    (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
171        $crate::graphql_value!(
172            @object $object
173            [$($key)+]
174            ($crate::graphql_value!({$($map)*})) $($rest)*
175        );
176    };
177
178    // Next value is an expression followed by comma.
179    (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
180        $crate::graphql_value!(
181            @object $object
182            [$($key)+]
183            ($crate::graphql_value!($value)) , $($rest)*
184        );
185    };
186
187    // Last value is an expression with no trailing comma.
188    (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
189        $crate::graphql_value!(
190            @object $object
191            [$($key)+]
192            ($crate::graphql_value!($value))
193        );
194    };
195
196    // Missing value for last entry. Trigger a reasonable error message.
197    (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
198        // "unexpected end of macro invocation"
199        $crate::graphql_value!();
200    };
201
202    // Missing colon and value for last entry. Trigger a reasonable error
203    // message.
204    (@object $object:ident ($($key:tt)+) () $copy:tt) => {
205        // "unexpected end of macro invocation"
206        $crate::graphql_value!();
207    };
208
209    // Misplaced colon. Trigger a reasonable error message.
210    (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
211        // Takes no arguments so "no rules expected the token `:`".
212        $crate::graphql_value!(@unexpected $colon);
213    };
214
215    // Found a comma inside a key. Trigger a reasonable error message.
216    (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
217        // Takes no arguments so "no rules expected the token `,`".
218        $crate::graphql_value!(@unexpected $comma);
219    };
220
221    // Key is fully parenthesized. This avoids `clippy::double_parens` false
222    // positives because the parenthesization may be necessary here.
223    (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
224        $crate::graphql_value!(@object $object ($key) (: $($rest)*) (: $($rest)*));
225    };
226
227    // Refuse to absorb colon token into key expression.
228    (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
229        $crate::graphql_value!(@unexpected $($unexpected)+);
230    };
231
232    // Munch a token into the current key.
233    (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
234        $crate::graphql_value!(
235            @object $object
236            ($($key)* $tt)
237            ($($rest)*) ($($rest)*)
238        );
239    };
240
241    ////////////
242    // Errors //
243    ////////////
244
245    (@unexpected) => {};
246
247    //////////////
248    // Defaults //
249    //////////////
250
251    ([ $($arr:tt)* ]$(,)?) => {
252        $crate::graphql_value!(@array [] $($arr)*)
253    };
254
255    ({}$(,)?) => {
256        $crate::Value::object($crate::Object::with_capacity(0))
257    };
258
259    ({ $($map:tt)+ }$(,)?) => {
260        $crate::Value::object({
261            let mut object = $crate::Object::with_capacity(0);
262            $crate::graphql_value!(@object object () ($($map)*) ($($map)*));
263            object
264        })
265    };
266
267    (null$(,)?) => ($crate::Value::null());
268
269    (None$(,)?) => ($crate::Value::null());
270
271    ($e:expr$(,)?) => ($crate::IntoValue::into_value($e));
272}
273
274#[cfg(test)]
275mod tests {
276    type V = crate::Value;
277
278    #[test]
279    fn null() {
280        assert_eq!(graphql_value!(null), V::Null);
281    }
282
283    #[test]
284    fn scalar() {
285        let val = 42;
286
287        assert_eq!(graphql_value!(1), V::scalar(1));
288        assert_eq!(graphql_value!("val"), V::scalar("val"));
289        assert_eq!(graphql_value!(1.34), V::scalar(1.34));
290        assert_eq!(graphql_value!(false), V::scalar(false));
291        assert_eq!(graphql_value!(1 + 2), V::scalar(3));
292        assert_eq!(graphql_value!(val), V::scalar(42));
293    }
294
295    #[test]
296    fn list() {
297        let val = 42;
298
299        assert_eq!(graphql_value!([]), V::list(vec![]));
300
301        assert_eq!(graphql_value!([null]), V::list(vec![V::Null]));
302
303        assert_eq!(graphql_value!([1]), V::list(vec![V::scalar(1)]));
304        assert_eq!(graphql_value!([1 + 2]), V::list(vec![V::scalar(3)]));
305        assert_eq!(graphql_value!([val]), V::list(vec![V::scalar(42)]));
306
307        assert_eq!(
308            graphql_value!([1, [2], 3]),
309            V::list(vec![
310                V::scalar(1),
311                V::list(vec![V::scalar(2)]),
312                V::scalar(3),
313            ]),
314        );
315        assert_eq!(
316            graphql_value!(["string", [2 + 3], true]),
317            V::list(vec![
318                V::scalar("string"),
319                V::list(vec![V::scalar(5)]),
320                V::scalar(true),
321            ]),
322        );
323    }
324
325    #[test]
326    fn object() {
327        let val = 42;
328
329        assert_eq!(
330            graphql_value!({}),
331            V::object(Vec::<(String, _)>::new().into_iter().collect()),
332        );
333        assert_eq!(
334            graphql_value!({ "key": null }),
335            V::object(vec![("key", V::Null)].into_iter().collect()),
336        );
337        assert_eq!(
338            graphql_value!({ "key": 123 }),
339            V::object(vec![("key", V::scalar(123))].into_iter().collect()),
340        );
341        assert_eq!(
342            graphql_value!({ "key": 1 + 2 }),
343            V::object(vec![("key", V::scalar(3))].into_iter().collect()),
344        );
345        assert_eq!(
346            graphql_value!({ "key": [] }),
347            V::object(vec![("key", V::list(vec![]))].into_iter().collect()),
348        );
349        assert_eq!(
350            graphql_value!({ "key": [null] }),
351            V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()),
352        );
353        assert_eq!(
354            graphql_value!({ "key": [1] }),
355            V::object(
356                vec![("key", V::list(vec![V::scalar(1)]))]
357                    .into_iter()
358                    .collect(),
359            ),
360        );
361        assert_eq!(
362            graphql_value!({ "key": [1 + 2] }),
363            V::object(
364                vec![("key", V::list(vec![V::scalar(3)]))]
365                    .into_iter()
366                    .collect(),
367            ),
368        );
369        assert_eq!(
370            graphql_value!({ "key": [val] }),
371            V::object(
372                vec![("key", V::list(vec![V::scalar(42)]))]
373                    .into_iter()
374                    .collect(),
375            ),
376        );
377    }
378
379    #[test]
380    fn option() {
381        let val = Some(42);
382
383        assert_eq!(graphql_value!(None), V::Null);
384        assert_eq!(graphql_value!(Some(42)), V::scalar(42));
385        assert_eq!(graphql_value!(val), V::scalar(42));
386    }
387}