Skip to content

Commit 8647d06

Browse files
committed
Fix interface macro syntax issues, add tests
1 parent 5caea1f commit 8647d06

File tree

3 files changed

+298
-3
lines changed

3 files changed

+298
-3
lines changed

src/macros/interface.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ macro_rules! graphql_interface {
175175
graphql_interface!(@gather_meta, $reg, $acc, $descr, $( $rest )*)
176176
};
177177

178-
( @gather_meta, $reg:expr, $acc:expr, $descr:expr, $(,)* ) => {};
178+
( @gather_meta, $reg:expr, $acc:expr, $descr:expr, , $( $rest:tt )* ) => {
179+
graphql_interface!(@gather_meta, $reg, $acc, $descr, $( $rest )*)
180+
};
181+
182+
( @gather_meta, $reg:expr, $acc:expr, $descr:expr, ) => {
183+
};
179184

180185
// field deprecated <reason> <name>(...) -> <type> as <description> { ... }
181186
(
@@ -269,7 +274,7 @@ macro_rules! graphql_interface {
269274
(
270275
@concrete_type_name,
271276
($outname:tt, $ctxtarg:ident, $ctxttype:ty),
272-
instance_resolvers : | $ctxtvar:pat | [ $( $resolver:expr , )* ] $( $rest:tt )*
277+
instance_resolvers : | $ctxtvar:pat | [ $( $resolver:expr ),* $(,)* ] $( $rest:tt )*
273278
) => {
274279
let $ctxtvar = &$ctxtarg;
275280

@@ -294,7 +299,7 @@ macro_rules! graphql_interface {
294299
(
295300
@resolve_into_type,
296301
($outname:tt, $typenamearg:ident, $execarg:ident, $ctxttype:ty),
297-
instance_resolvers : | $ctxtvar:pat | [ $( $resolver:expr , )* ] $( $rest:tt )*
302+
instance_resolvers : | $ctxtvar:pat | [ $( $resolver:expr ),* $(,)* ] $( $rest:tt )*
298303
) => {
299304
let $ctxtvar = &$execarg.context();
300305

@@ -362,6 +367,12 @@ macro_rules! graphql_interface {
362367
$($items)*);
363368
}
364369
});
370+
371+
impl<$($lifetime)*> $crate::IntoFieldResult<$name> for $name {
372+
fn into(self) -> $crate::FieldResult<$name> {
373+
Ok(self)
374+
}
375+
}
365376
};
366377

367378
(
@@ -380,4 +391,12 @@ macro_rules! graphql_interface {
380391
) => {
381392
graphql_interface!(() $name : $ctxt as $outname | &$mainself | { $( $items )* });
382393
};
394+
395+
(
396+
$name:ty : $ctxt:ty | &$mainself:ident | {
397+
$( $items:tt )*
398+
}
399+
) => {
400+
graphql_interface!(() $name : $ctxt as (stringify!($name)) | &$mainself | { $( $items )* });
401+
};
383402
}

src/macros/tests/interface.rs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
use std::collections::HashMap;
2+
use std::marker::PhantomData;
3+
4+
use ast::InputValue;
5+
use value::Value;
6+
use schema::model::RootNode;
7+
8+
/*
9+
10+
Syntax to validate:
11+
12+
* Order of items: fields, description, instance resolvers
13+
* Optional Generics/lifetimes
14+
* Custom name vs. default name
15+
* Optional commas between items
16+
* Optional trailing commas on instance resolvers
17+
18+
*/
19+
20+
struct Concrete;
21+
22+
struct DefaultName;
23+
24+
#[allow(dead_code)]
25+
struct WithLifetime<'a> { data: PhantomData<&'a i64> }
26+
27+
#[allow(dead_code)]
28+
struct WithGenerics<T> { data: T }
29+
30+
struct DescriptionFirst;
31+
struct FieldsFirst;
32+
struct InterfacesFirst;
33+
34+
struct CommasWithTrailing;
35+
struct CommasOnMeta;
36+
37+
struct ResolversWithTrailingComma;
38+
39+
struct Root;
40+
41+
graphql_object!(Concrete: () |&self| {
42+
field simple() -> i64 { 0 }
43+
});
44+
45+
graphql_interface!(DefaultName: () |&self| {
46+
field simple() -> i64 { 0 }
47+
48+
instance_resolvers: |_| [ Some(Concrete) ]
49+
});
50+
51+
52+
graphql_interface!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
53+
field simple() -> i64 { 0 }
54+
instance_resolvers: |_| [ Some(Concrete) ]
55+
});
56+
57+
graphql_interface!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
58+
field simple() -> i64 { 0 }
59+
instance_resolvers: |_| [ Some(Concrete) ]
60+
});
61+
62+
63+
graphql_interface!(DescriptionFirst: () as "DescriptionFirst" |&self| {
64+
description: "A description"
65+
66+
field simple() -> i64 { 0 }
67+
68+
instance_resolvers: |_| [ Some(Concrete) ]
69+
});
70+
71+
graphql_interface!(FieldsFirst: () as "FieldsFirst" |&self| {
72+
field simple() -> i64 { 0 }
73+
74+
description: "A description"
75+
76+
instance_resolvers: |_| [ Some(Concrete) ]
77+
});
78+
79+
graphql_interface!(InterfacesFirst: () as "InterfacesFirst" |&self| {
80+
instance_resolvers: |_| [ Some(Concrete) ]
81+
82+
field simple() -> i64 { 0 }
83+
84+
description: "A description"
85+
});
86+
87+
graphql_interface!(CommasWithTrailing: () as "CommasWithTrailing" |&self| {
88+
instance_resolvers: |_| [ Some(Concrete) ],
89+
90+
field simple() -> i64 { 0 },
91+
92+
description: "A description",
93+
});
94+
95+
96+
graphql_interface!(CommasOnMeta: () as "CommasOnMeta" |&self| {
97+
instance_resolvers: |_| [ Some(Concrete) ]
98+
description: "A description",
99+
100+
field simple() -> i64 { 0 }
101+
});
102+
103+
104+
graphql_interface!(ResolversWithTrailingComma: () as "ResolversWithTrailingComma" |&self| {
105+
instance_resolvers: |_| [ Some(Concrete), ]
106+
description: "A description",
107+
108+
field simple() -> i64 { 0 }
109+
});
110+
111+
graphql_object!(<'a> Root: () as "Root" |&self| {
112+
field default_name() -> DefaultName { DefaultName {} }
113+
114+
field with_lifetime() -> WithLifetime<'a> { WithLifetime { data: PhantomData } }
115+
field with_generics() -> WithGenerics<i64> { WithGenerics { data: 123 } }
116+
117+
field description_first() -> DescriptionFirst { DescriptionFirst {} }
118+
field fields_first() -> FieldsFirst { FieldsFirst {} }
119+
field interfaces_first() -> InterfacesFirst { InterfacesFirst {} }
120+
121+
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} }
122+
field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} }
123+
124+
field resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
125+
ResolversWithTrailingComma {}
126+
}
127+
128+
});
129+
130+
131+
fn run_type_info_query<F>(type_name: &str, f: F)
132+
where F: Fn(&HashMap<String, Value>, &Vec<Value>) -> ()
133+
{
134+
let doc = r#"
135+
query ($typeName: String!) {
136+
__type(name: $typeName) {
137+
name
138+
description
139+
fields(includeDeprecated: true) {
140+
name
141+
}
142+
}
143+
}
144+
"#;
145+
let schema = RootNode::new(Root {}, ());
146+
let vars = vec![
147+
("typeName".to_owned(), InputValue::string(type_name)),
148+
].into_iter().collect();
149+
150+
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
151+
.expect("Execution failed");
152+
153+
assert_eq!(errs, []);
154+
155+
println!("Result: {:?}", result);
156+
157+
let type_info = result
158+
.as_object_value().expect("Result is not an object")
159+
.get("__type").expect("__type field missing")
160+
.as_object_value().expect("__type field not an object value");
161+
162+
let fields = type_info
163+
.get("fields").expect("fields field missing")
164+
.as_list_value().expect("fields field not a list value");
165+
166+
f(type_info, fields);
167+
}
168+
169+
#[test]
170+
fn introspect_default_name() {
171+
run_type_info_query("DefaultName", |object, fields| {
172+
assert_eq!(object.get("name"), Some(&Value::string("DefaultName")));
173+
assert_eq!(object.get("description"), Some(&Value::null()));
174+
175+
assert!(fields.contains(&Value::object(vec![
176+
("name", Value::string("simple")),
177+
].into_iter().collect())));
178+
});
179+
}
180+
181+
#[test]
182+
fn introspect_with_lifetime() {
183+
run_type_info_query("WithLifetime", |object, fields| {
184+
assert_eq!(object.get("name"), Some(&Value::string("WithLifetime")));
185+
assert_eq!(object.get("description"), Some(&Value::null()));
186+
187+
assert!(fields.contains(&Value::object(vec![
188+
("name", Value::string("simple")),
189+
].into_iter().collect())));
190+
});
191+
}
192+
193+
#[test]
194+
fn introspect_with_generics() {
195+
run_type_info_query("WithGenerics", |object, fields| {
196+
assert_eq!(object.get("name"), Some(&Value::string("WithGenerics")));
197+
assert_eq!(object.get("description"), Some(&Value::null()));
198+
199+
assert!(fields.contains(&Value::object(vec![
200+
("name", Value::string("simple")),
201+
].into_iter().collect())));
202+
});
203+
}
204+
205+
#[test]
206+
fn introspect_description_first() {
207+
run_type_info_query("DescriptionFirst", |object, fields| {
208+
assert_eq!(object.get("name"), Some(&Value::string("DescriptionFirst")));
209+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
210+
211+
assert!(fields.contains(&Value::object(vec![
212+
("name", Value::string("simple")),
213+
].into_iter().collect())));
214+
});
215+
}
216+
217+
#[test]
218+
fn introspect_fields_first() {
219+
run_type_info_query("FieldsFirst", |object, fields| {
220+
assert_eq!(object.get("name"), Some(&Value::string("FieldsFirst")));
221+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
222+
223+
assert!(fields.contains(&Value::object(vec![
224+
("name", Value::string("simple")),
225+
].into_iter().collect())));
226+
});
227+
}
228+
229+
#[test]
230+
fn introspect_interfaces_first() {
231+
run_type_info_query("InterfacesFirst", |object, fields| {
232+
assert_eq!(object.get("name"), Some(&Value::string("InterfacesFirst")));
233+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
234+
235+
assert!(fields.contains(&Value::object(vec![
236+
("name", Value::string("simple")),
237+
].into_iter().collect())));
238+
});
239+
}
240+
241+
#[test]
242+
fn introspect_commas_with_trailing() {
243+
run_type_info_query("CommasWithTrailing", |object, fields| {
244+
assert_eq!(object.get("name"), Some(&Value::string("CommasWithTrailing")));
245+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
246+
247+
assert!(fields.contains(&Value::object(vec![
248+
("name", Value::string("simple")),
249+
].into_iter().collect())));
250+
});
251+
}
252+
253+
#[test]
254+
fn introspect_commas_on_meta() {
255+
run_type_info_query("CommasOnMeta", |object, fields| {
256+
assert_eq!(object.get("name"), Some(&Value::string("CommasOnMeta")));
257+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
258+
259+
assert!(fields.contains(&Value::object(vec![
260+
("name", Value::string("simple")),
261+
].into_iter().collect())));
262+
});
263+
}
264+
265+
#[test]
266+
fn introspect_resolvers_with_trailing_comma() {
267+
run_type_info_query("ResolversWithTrailingComma", |object, fields| {
268+
assert_eq!(object.get("name"), Some(&Value::string("ResolversWithTrailingComma")));
269+
assert_eq!(object.get("description"), Some(&Value::string("A description")));
270+
271+
assert!(fields.contains(&Value::object(vec![
272+
("name", Value::string("simple")),
273+
].into_iter().collect())));
274+
});
275+
}

src/macros/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ mod scalar;
44
mod args;
55
mod field;
66
mod object;
7+
mod interface;

0 commit comments

Comments
 (0)