Skip to content

Commit bfed867

Browse files
committed
Add support for interfaces
1 parent 23643fb commit bfed867

File tree

13 files changed

+220
-48
lines changed

13 files changed

+220
-48
lines changed

graphql_query_derive/src/constants.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use field_type::FieldType;
2+
use objects::GqlObjectField;
13
use proc_macro2::{Ident, Span};
24

35
pub const TYPENAME_FIELD: &'static str = "__typename";
@@ -9,3 +11,12 @@ pub fn string_type() -> Ident {
911
pub fn float_type() -> Ident {
1012
Ident::new("Float", Span::call_site())
1113
}
14+
15+
pub fn typename_field() -> GqlObjectField {
16+
GqlObjectField {
17+
name: TYPENAME_FIELD.to_string(),
18+
/// Non-nullable, see spec:
19+
/// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md
20+
type_: FieldType::Named(string_type()),
21+
}
22+
}

graphql_query_derive/src/field_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use proc_macro2::{Ident, Span, TokenStream};
55
use query::QueryContext;
66
use schema::DEFAULT_SCALARS;
77

8-
#[derive(Debug, PartialEq, Hash)]
8+
#[derive(Clone, Debug, PartialEq, Hash)]
99
pub enum FieldType {
1010
Named(Ident),
1111
Optional(Box<FieldType>),
Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
use constants::*;
2+
use failure;
13
use objects::GqlObjectField;
24
use proc_macro2::{Ident, Span, TokenStream};
35
use query::QueryContext;
4-
use selection::Selection;
6+
use selection::{Selection, SelectionItem};
7+
use shared::*;
8+
use std::borrow::Cow;
9+
use unions::union_variants;
510

611
#[derive(Debug, PartialEq)]
712
pub struct GqlInterface {
@@ -11,16 +16,81 @@ pub struct GqlInterface {
1116
}
1217

1318
impl GqlInterface {
19+
pub fn new(name: Cow<str>) -> GqlInterface {
20+
GqlInterface {
21+
name: name.into_owned(),
22+
implemented_by: Vec::new(),
23+
fields: vec![],
24+
}
25+
}
26+
1427
pub fn response_for_selection(
1528
&self,
16-
_query_context: &QueryContext,
17-
_selection: &Selection,
29+
query_context: &QueryContext,
30+
selection: &Selection,
1831
prefix: &str,
19-
) -> TokenStream {
32+
) -> Result<TokenStream, failure::Error> {
2033
let name = Ident::new(&prefix, Span::call_site());
21-
quote! {
22-
#[derive(Debug, Deserialize)]
23-
pub struct #name;
24-
}
34+
35+
selection
36+
.extract_typename()
37+
.ok_or_else(|| format_err!("Missing __typename in selection for {}", prefix))?;
38+
39+
let object_selection = Selection(
40+
selection.0.iter()
41+
// Only keep what we can handle
42+
.filter(|f| match f {
43+
SelectionItem::Field(f) => f.name != "__typename",
44+
SelectionItem::FragmentSpread(_) => true,
45+
SelectionItem::InlineFragment(_) => false,
46+
}).map(|a| (*a).clone()).collect(),
47+
);
48+
49+
let union_selection = Selection(
50+
selection.0.iter()
51+
// Only keep what we can handle
52+
.filter(|f| match f {
53+
SelectionItem::InlineFragment(_) => true,
54+
SelectionItem::Field(_) | SelectionItem::FragmentSpread(_) => false,
55+
}).map(|a| (*a).clone()).collect(),
56+
);
57+
58+
let object_fields =
59+
response_fields_for_selection(&self.fields, query_context, &object_selection, prefix)?;
60+
61+
let object_children =
62+
field_impls_for_selection(&self.fields, query_context, &object_selection, prefix)?;
63+
let (union_variants, union_children) =
64+
union_variants(&union_selection, query_context, prefix)?;
65+
66+
let attached_enum_name = Ident::new(&format!("{}On", name), Span::call_site());
67+
let (attached_enum, last_object_field) = if union_variants.len() > 0 {
68+
let attached_enum = quote! {
69+
#[derive(Deserialize, Debug, Serialize)]
70+
#[serde(tag = "__typename")]
71+
pub enum #attached_enum_name {
72+
#(#union_variants,)*
73+
}
74+
};
75+
let last_object_field = quote!(#[serde(flatten)] on: #attached_enum_name,);
76+
(attached_enum, last_object_field)
77+
} else {
78+
(quote!(), quote!())
79+
};
80+
81+
Ok(quote! {
82+
83+
#(#object_children)*
84+
85+
#(#union_children)*
86+
87+
#attached_enum
88+
89+
#[derive(Debug, Serialize, Deserialize)]
90+
pub struct #name {
91+
#(#object_fields,)*
92+
#last_object_field
93+
}
94+
})
2595
}
2696
}

graphql_query_derive/src/objects.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub struct GqlObject {
1313
pub fields: Vec<GqlObjectField>,
1414
}
1515

16-
#[derive(Debug, PartialEq, Hash)]
16+
#[derive(Clone, Debug, PartialEq, Hash)]
1717
pub struct GqlObjectField {
1818
pub name: String,
1919
pub type_: FieldType,
@@ -23,12 +23,7 @@ impl GqlObject {
2323
pub fn new(name: Cow<str>) -> GqlObject {
2424
GqlObject {
2525
name: name.into_owned(),
26-
fields: vec![GqlObjectField {
27-
name: TYPENAME_FIELD.to_string(),
28-
/// Non-nullable, see spec:
29-
/// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md
30-
type_: FieldType::Named(string_type()),
31-
}],
26+
fields: vec![typename_field()],
3227
}
3328
}
3429

graphql_query_derive/src/query.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl QueryContext {
9292
} else if let Some(obj) = self.schema.objects.get(ty) {
9393
obj.response_for_selection(self, &selection, prefix)
9494
} else if let Some(iface) = self.schema.interfaces.get(ty) {
95-
Ok(iface.response_for_selection(self, &selection, prefix))
95+
iface.response_for_selection(self, &selection, prefix)
9696
} else if let Some(unn) = self.schema.unions.get(ty) {
9797
unn.response_for_selection(self, &selection, prefix)
9898
} else {

graphql_query_derive/src/schema.rs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -234,21 +234,14 @@ impl ::std::convert::From<graphql_parser::schema::Document> for Schema {
234234
schema.unions.insert(union.name, GqlUnion(tys));
235235
}
236236
schema::TypeDefinition::Interface(interface) => {
237-
schema.interfaces.insert(
238-
interface.name.clone(),
239-
GqlInterface {
240-
name: interface.name,
241-
implemented_by: Vec::new(),
242-
fields: interface
243-
.fields
244-
.iter()
245-
.map(|f| GqlObjectField {
246-
name: f.name.clone(),
247-
type_: FieldType::from(f.field_type.clone()),
248-
})
249-
.collect(),
250-
},
251-
);
237+
let mut iface = GqlInterface::new(interface.name.clone().into());
238+
iface
239+
.fields
240+
.extend(interface.fields.iter().map(|f| GqlObjectField {
241+
name: f.name.clone(),
242+
type_: FieldType::from(f.field_type.clone()),
243+
}));
244+
schema.interfaces.insert(interface.name, iface);
252245
}
253246
schema::TypeDefinition::InputObject(input) => {
254247
schema
@@ -342,21 +335,18 @@ impl ::std::convert::From<::introspection_response::IntrospectionResponse> for S
342335
.insert(name.clone(), GqlObject::from_introspected_schema_json(ty));
343336
}
344337
Some(__TypeKind::INTERFACE) => {
345-
let iface = GqlInterface {
346-
name: name.clone(),
347-
implemented_by: Vec::new(),
348-
fields: ty
349-
.fields
338+
let mut iface = GqlInterface::new(name.clone().into());
339+
iface.fields.extend(
340+
ty.fields
350341
.clone()
351342
.expect("interface fields")
352343
.into_iter()
353344
.filter_map(|f| f)
354345
.map(|f| GqlObjectField {
355346
name: f.name.expect("field name"),
356347
type_: FieldType::from(f.type_.expect("field type")),
357-
})
358-
.collect(),
359-
};
348+
}),
349+
);
360350
schema.interfaces.insert(name, iface);
361351
}
362352
Some(__TypeKind::INPUT_OBJECT) => {

graphql_query_derive/src/selection.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
use constants::*;
22
use graphql_parser::query::SelectionSet;
33

4-
#[derive(Debug, PartialEq)]
4+
#[derive(Clone, Debug, PartialEq)]
55
pub struct SelectionField {
66
pub name: String,
77
pub fields: Selection,
88
}
99

10-
#[derive(Debug, PartialEq)]
10+
#[derive(Clone, Debug, PartialEq)]
1111
pub struct SelectionFragmentSpread {
1212
pub fragment_name: String,
1313
}
1414

15-
#[derive(Debug, PartialEq)]
15+
#[derive(Clone, Debug, PartialEq)]
1616
pub struct SelectionInlineFragment {
1717
pub on: String,
1818
pub fields: Selection,
1919
}
2020

21-
#[derive(Debug, PartialEq)]
21+
#[derive(Clone, Debug, PartialEq)]
2222
pub enum SelectionItem {
2323
Field(SelectionField),
2424
FragmentSpread(SelectionFragmentSpread),
@@ -36,7 +36,7 @@ impl SelectionItem {
3636
}
3737
}
3838

39-
#[derive(Debug, PartialEq)]
39+
#[derive(Clone, Debug, PartialEq)]
4040
pub struct Selection(pub Vec<SelectionItem>);
4141

4242
impl Selection {

graphql_query_derive/src/shared.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ pub fn response_fields_for_selection(
8282
#field_name: #type_name
8383
})
8484
}
85-
SelectionItem::InlineFragment(_) => unreachable!("inline fragment on object field"),
85+
SelectionItem::InlineFragment(_) => {
86+
Err(format_err!("inline fragment on object field"))?
87+
}
8688
})
8789
.collect()
8890
}

graphql_query_derive/src/unions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ pub fn union_variants(
3737
})
3838
.map(|item| {
3939
match item {
40-
SelectionItem::Field(_) => panic!("field selection on union"),
41-
SelectionItem::FragmentSpread(_) => panic!("fragment spread on union"),
40+
SelectionItem::Field(_) => Err(format_err!("field selection on union"))?,
41+
SelectionItem::FragmentSpread(_) => Err(format_err!("fragment spread on union"))?,
4242
SelectionItem::InlineFragment(frag) => {
4343
let variant_name = Ident::new(&frag.on, Span::call_site());
4444

tests/interfaces.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#[macro_use]
2+
extern crate graphql_client;
3+
#[macro_use]
4+
extern crate serde_derive;
5+
extern crate serde;
6+
extern crate serde_json;
7+
8+
const RESPONSE: &'static str = include_str!("interfaces/interface_response.json");
9+
10+
#[derive(GraphQLQuery)]
11+
#[GraphQLQuery(
12+
query_path = "tests/interfaces/interface_query.graphql",
13+
schema_path = "tests/interfaces/interface_schema.graphql"
14+
)]
15+
#[allow(dead_code)]
16+
struct InterfaceQuery;
17+
18+
#[test]
19+
fn interface_deserialization() {
20+
println!("{:?}", RESPONSE);
21+
let response_data: interface_query::ResponseData = serde_json::from_str(RESPONSE).unwrap();
22+
23+
println!("{:?}", response_data);
24+
25+
let expected = r##"ResponseData { everything: Some([RustMyQueryEverything { name: "Audrey Lorde", on: Person(RustMyQueryEverythingOnPerson { birthday: Some("1934-02-18") }) }, RustMyQueryEverything { name: "Laïka", on: Dog(RustMyQueryEverythingOnDog { isGoodDog: true }) }, RustMyQueryEverything { name: "Mozilla", on: Organization(RustMyQueryEverythingOnOrganization { industry: OTHER }) }, RustMyQueryEverything { name: "Norbert", on: Dog(RustMyQueryEverythingOnDog { isGoodDog: true }) }]) }"##;
26+
27+
assert_eq!(format!("{:?}", response_data), expected);
28+
29+
assert_eq!(response_data.everything.map(|names| names.len()), Some(4));
30+
}

0 commit comments

Comments
 (0)