Skip to content

Commit 443c1e9

Browse files
authored
Merge pull request #27 from tomhoule/interfaces
Support interfaces
2 parents e9456d1 + 21e2e92 commit 443c1e9

17 files changed

+459
-148
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>),

graphql_query_derive/src/fragments.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,19 @@ pub struct GqlFragment {
1010
}
1111

1212
impl GqlFragment {
13-
pub fn to_rust(&self, context: &QueryContext) -> TokenStream {
13+
pub fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
1414
let name_ident = Ident::new(&self.name, Span::call_site());
1515
let object = context.schema.objects.get(&self.on).expect("oh, noes");
16-
let field_impls = object
17-
.field_impls_for_selection(context, &self.selection, &self.name)
18-
.unwrap();
19-
let fields = object.response_fields_for_selection(context, &self.selection, &self.name);
16+
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;
17+
let fields = object.response_fields_for_selection(context, &self.selection, &self.name)?;
2018

21-
quote!{
19+
Ok(quote!{
2220
#[derive(Debug, Deserialize, Serialize)]
2321
pub struct #name_ident {
2422
#(#fields,)*
2523
}
2624

2725
#(#field_impls)*
28-
}
26+
})
2927
}
3028
}
Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,106 @@
1+
use failure;
12
use objects::GqlObjectField;
23
use proc_macro2::{Ident, Span, TokenStream};
34
use query::QueryContext;
4-
use selection::Selection;
5+
use selection::{Selection, SelectionItem};
6+
use shared::*;
7+
use std::borrow::Cow;
8+
use std::collections::HashSet;
9+
use unions::union_variants;
510

611
#[derive(Debug, PartialEq)]
712
pub struct GqlInterface {
8-
pub implemented_by: Vec<String>,
13+
pub implemented_by: HashSet<String>,
914
pub name: String,
1015
pub fields: Vec<GqlObjectField>,
1116
}
1217

1318
impl GqlInterface {
19+
pub fn new(name: Cow<str>) -> GqlInterface {
20+
GqlInterface {
21+
name: name.into_owned(),
22+
implemented_by: HashSet::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 (mut union_variants, union_children, used_variants) =
64+
union_variants(&union_selection, query_context, prefix)?;
65+
66+
union_variants.extend(
67+
self.implemented_by
68+
.iter()
69+
.filter(|obj| used_variants.iter().find(|v| v == obj).is_none())
70+
.map(|v| {
71+
let v = Ident::new(v, Span::call_site());
72+
quote!(#v)
73+
}),
74+
);
75+
76+
let attached_enum_name = Ident::new(&format!("{}On", name), Span::call_site());
77+
let (attached_enum, last_object_field) = if union_variants.len() > 0 {
78+
let attached_enum = quote! {
79+
#[derive(Deserialize, Debug, Serialize)]
80+
#[serde(tag = "__typename")]
81+
pub enum #attached_enum_name {
82+
#(#union_variants,)*
83+
}
84+
};
85+
let last_object_field = quote!(#[serde(flatten)] on: #attached_enum_name,);
86+
(attached_enum, last_object_field)
87+
} else {
88+
(quote!(), quote!())
89+
};
90+
91+
Ok(quote! {
92+
93+
#(#object_children)*
94+
95+
#(#union_children)*
96+
97+
#attached_enum
98+
99+
#[derive(Debug, Serialize, Deserialize)]
100+
pub struct #name {
101+
#(#object_fields,)*
102+
#last_object_field
103+
}
104+
})
25105
}
26106
}

graphql_query_derive/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ fn read_file(
6666
}
6767

6868
fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Error> {
69-
use std::io::prelude::*;
70-
7169
let cargo_manifest_dir =
7270
::std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env variable is defined");
7371

graphql_query_derive/src/objects.rs

Lines changed: 7 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use constants::*;
22
use failure;
33
use field_type::FieldType;
4-
use heck::{CamelCase, SnakeCase};
54
use proc_macro2::{Ident, Span, TokenStream};
65
use query::QueryContext;
76
use selection::*;
8-
use shared::render_object_field;
7+
use shared::{field_impls_for_selection, response_fields_for_selection};
98
use std::borrow::Cow;
109

1110
#[derive(Debug, PartialEq)]
@@ -14,7 +13,7 @@ pub struct GqlObject {
1413
pub fields: Vec<GqlObjectField>,
1514
}
1615

17-
#[derive(Debug, PartialEq, Hash)]
16+
#[derive(Clone, Debug, PartialEq, Hash)]
1817
pub struct GqlObjectField {
1918
pub name: String,
2019
pub type_: FieldType,
@@ -24,12 +23,7 @@ impl GqlObject {
2423
pub fn new(name: Cow<str>) -> GqlObject {
2524
GqlObject {
2625
name: name.into_owned(),
27-
fields: vec![GqlObjectField {
28-
name: TYPENAME_FIELD.to_string(),
29-
/// Non-nullable, see spec:
30-
/// https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md
31-
type_: FieldType::Named(string_type()),
32-
}],
26+
fields: vec![typename_field()],
3327
}
3428
}
3529

@@ -64,7 +58,7 @@ impl GqlObject {
6458
prefix: &str,
6559
) -> Result<TokenStream, failure::Error> {
6660
let name = Ident::new(prefix, Span::call_site());
67-
let fields = self.response_fields_for_selection(query_context, selection, prefix);
61+
let fields = self.response_fields_for_selection(query_context, selection, prefix)?;
6862
let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?;
6963
Ok(quote! {
7064
#(#field_impls)*
@@ -82,69 +76,15 @@ impl GqlObject {
8276
selection: &Selection,
8377
prefix: &str,
8478
) -> Result<Vec<TokenStream>, failure::Error> {
85-
selection
86-
.0
87-
.iter()
88-
.map(|selected| {
89-
if let SelectionItem::Field(selected) = selected {
90-
let ty = self
91-
.fields
92-
.iter()
93-
.find(|f| f.name == selected.name)
94-
.ok_or_else(|| format_err!("could not find field `{}`", selected.name))?
95-
.type_
96-
.inner_name_string();
97-
let prefix = format!(
98-
"{}{}",
99-
prefix.to_camel_case(),
100-
selected.name.to_camel_case()
101-
);
102-
query_context.maybe_expand_field(&ty, &selected.fields, &prefix)
103-
} else {
104-
Ok(quote!())
105-
}
106-
})
107-
.collect()
79+
field_impls_for_selection(&self.fields, query_context, selection, prefix)
10880
}
10981

11082
pub fn response_fields_for_selection(
11183
&self,
11284
query_context: &QueryContext,
11385
selection: &Selection,
11486
prefix: &str,
115-
) -> Vec<TokenStream> {
116-
let mut fields = Vec::new();
117-
118-
for item in selection.0.iter() {
119-
match item {
120-
SelectionItem::Field(f) => {
121-
let name = &f.name;
122-
123-
let ty = &self
124-
.fields
125-
.iter()
126-
.find(|field| field.name.as_str() == name.as_str())
127-
.expect("could not find field")
128-
.type_;
129-
let ty = ty.to_rust(
130-
query_context,
131-
&format!("{}{}", prefix.to_camel_case(), name.to_camel_case()),
132-
);
133-
fields.push(render_object_field(name, ty));
134-
}
135-
SelectionItem::FragmentSpread(fragment) => {
136-
let field_name =
137-
Ident::new(&fragment.fragment_name.to_snake_case(), Span::call_site());
138-
let type_name = Ident::new(&fragment.fragment_name, Span::call_site());
139-
fields.push(quote!{
140-
#[serde(flatten)]
141-
#field_name: #type_name
142-
})
143-
}
144-
SelectionItem::InlineFragment(_) => unreachable!("inline fragment on object field"),
145-
}
146-
}
147-
148-
fields
87+
) -> Result<Vec<TokenStream>, failure::Error> {
88+
response_fields_for_selection(&self.fields, query_context, selection, prefix)
14989
}
15090
}

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 {

0 commit comments

Comments
 (0)