Skip to content

Commit 9f5294a

Browse files
committed
Add input object helper macro
1 parent c173554 commit 9f5294a

File tree

4 files changed

+563
-0
lines changed

4 files changed

+563
-0
lines changed

src/macros/input_object.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
Create an input object
3+
4+
Input objects are used as data carriers for complex input values to
5+
fields and mutations. Unlike the other helper macros,
6+
`graphql_input_object!` actually *creates* the struct you define. It
7+
does not add anything to the struct definition itself - what you type
8+
is what will be generated:
9+
10+
```rust
11+
# #[macro_use] extern crate juniper;
12+
13+
graphql_input_object!(
14+
description: "Coordinates for the user"
15+
16+
struct Coordinates {
17+
longitude: f64 as "The X coordinate, from -180 to +180",
18+
latitude: f64 as "The Y coordinate, from -90 to +90",
19+
}
20+
);
21+
22+
# fn main() { }
23+
```
24+
25+
This macro creates the struct as specified and implements
26+
`FromInputValue` to automatically parse values provided from variables
27+
and arguments.
28+
29+
If you want to expose the struct under a different name than the Rust
30+
type, you can write `struct Coordinates as "MyCoordinates" { ...`.
31+
32+
*/
33+
#[macro_export]
34+
macro_rules! graphql_input_object {
35+
// Calls $val.$func($arg) if $arg is not None
36+
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
37+
( @maybe_apply, $arg:tt, $func:ident, $val:expr ) => { $val.$func($arg) };
38+
39+
// Calls $val.description($descr) when $descr is not empty
40+
( @apply_description, , $val:expr ) => { $val };
41+
( @apply_description, $descr:tt , $val:expr ) => { $val.description($descr) };
42+
43+
// Generate the FromInputValue::from method body, provided a
44+
// HashMap<&str, &InputValue> in $var
45+
(
46+
@generate_from_input_value,
47+
$name:tt, $var:tt,
48+
( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* )
49+
) => {
50+
Some($name {
51+
$( $field_name: {
52+
let n: String = $crate::to_snake_case(stringify!($field_name));
53+
let v: &$crate::InputValue = $var[&n[..]];
54+
$crate::FromInputValue::from(v).unwrap()
55+
} ),*
56+
})
57+
};
58+
59+
// Generate the struct declaration, including (Rust) meta attributes
60+
(
61+
@generate_struct_fields,
62+
( $($meta:tt)* ), $name:tt,
63+
( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* )
64+
) => {
65+
$($meta)* struct $name {
66+
$( $field_name: $field_type, )*
67+
}
68+
};
69+
70+
// Generate the input field meta list, i.e. &[Argument].
71+
(
72+
@generate_meta_fields,
73+
$reg:tt,
74+
( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* )
75+
) => {
76+
&[
77+
$(
78+
graphql_input_object!(
79+
@apply_description,
80+
$($descr)*,
81+
$reg.arg::<$field_type>(
82+
&$crate::to_snake_case(stringify!($field_name))))
83+
),*
84+
]
85+
};
86+
87+
// #[...] struct $name { ... }
88+
// struct $name { ... }
89+
(
90+
@parse,
91+
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $descr:tt ),
92+
$(#[$meta:meta])* struct $name:ident { $($fields:tt)* } $($rest:tt)*
93+
) => {
94+
graphql_input_object!(
95+
@parse,
96+
( ( $(#[$meta])* ), $name, (stringify!($name)), ($($fields)*), $descr ),
97+
$($rest)*
98+
);
99+
};
100+
101+
// #[...] struct $name as "GraphQLName" { ... }
102+
// struct $name as "GraphQLName" { ... }
103+
(
104+
@parse,
105+
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $descr:tt ),
106+
$(#[$meta:meta])* struct $name:ident as $outname:tt { $($fields:tt)* } $($rest:tt)*
107+
) => {
108+
graphql_input_object!(
109+
@parse,
110+
( ( $($meta)* ), $name, $outname, ($($fields)*), $descr ),
111+
$($rest)*
112+
);
113+
};
114+
115+
// description: <description>
116+
(
117+
@parse,
118+
( $meta:tt, $name:tt, $outname:tt, $fields:tt, $_ignore:tt ),
119+
description: $descr:tt $($rest:tt)*
120+
) => {
121+
graphql_input_object!(
122+
@parse,
123+
( $meta, $name, $outname, $fields, $descr ),
124+
$($rest)*
125+
);
126+
};
127+
128+
// No more data to parse, generate the struct and impls
129+
(
130+
@parse,
131+
( $meta:tt, $name:tt, $outname:tt, $fields:tt, $descr:tt ),
132+
) => {
133+
graphql_input_object!(@generate_struct_fields, $meta, $name, $fields);
134+
135+
impl $crate::FromInputValue for $name {
136+
fn from(value: &$crate::InputValue) -> Option<$name> {
137+
if let Some(obj) = value.to_object_value() {
138+
graphql_input_object!(@generate_from_input_value, $name, obj, $fields)
139+
}
140+
else {
141+
None
142+
}
143+
}
144+
}
145+
146+
impl<CtxT> $crate::GraphQLType<CtxT> for $name {
147+
fn name() -> Option<&'static str> {
148+
Some($outname)
149+
}
150+
151+
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
152+
graphql_input_object!(
153+
@maybe_apply, $descr, description,
154+
registry.build_input_object_type::<$name>()(
155+
graphql_input_object!(@generate_meta_fields, registry, $fields)
156+
)).into_meta()
157+
}
158+
}
159+
};
160+
161+
// Entry point: parse calls starting with the struct declaration
162+
( $(#[$meta:meta])* struct $($items:tt)* ) => {
163+
graphql_input_object!(
164+
@parse,
165+
( ( ), None, None, None, None ),
166+
$(#[$meta])* struct $($items)*
167+
);
168+
};
169+
170+
// Entry point: parse calls starting with the description
171+
( description: $($items:tt)* ) => {
172+
graphql_input_object!(
173+
@parse,
174+
( ( ), None, None, None, None ),
175+
description: $($items)*
176+
);
177+
};
178+
}

src/macros/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
#[macro_use] mod scalar;
55
#[macro_use] mod args;
66
#[macro_use] mod field;
7+
#[macro_use] mod input_object;
78

89
#[cfg(test)] mod tests;

0 commit comments

Comments
 (0)