Skip to content

Commit 52aa20c

Browse files
committed
Implement fragment on interfaces
This is limited to the interface’s own fields for now. We do not support type-refining fragments (see this issue: #154).
1 parent f34451f commit 52aa20c

File tree

8 files changed

+117
-47
lines changed

8 files changed

+117
-47
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/target
1+
target/
22
node_modules/
33
**/*.rs.bk
44
Cargo.lock

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5252

5353
- `GraphQLError` now implements the `Display` trait.
5454

55+
- Basic support for fragments on interfaces
56+
5557
### Fixed
5658

5759
- Handle all Rust keywords as field names in codegen by appending `_` to the generated names, so a field called `type` in a GraphQL query will become a `type_` field in the generated struct. Thanks to @scrogson!

examples/call_from_js/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![feature(wasm_custom_section, wasm_import_module, use_extern_macros)]
1+
#![feature(use_extern_macros)]
22

33
#[macro_use]
44
extern crate graphql_client;

graphql_client_codegen/src/fragments.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,24 @@ impl GqlFragment {
1616
let derives = context.response_derives();
1717
let name_ident = Ident::new(&self.name, Span::call_site());
1818
let opt_object = context.schema.objects.get(&self.on);
19-
let object = if let Some(object) = opt_object {
20-
object
19+
let (field_impls, fields) = if let Some(object) = opt_object {
20+
let field_impls =
21+
object.field_impls_for_selection(context, &self.selection, &self.name)?;
22+
let fields =
23+
object.response_fields_for_selection(context, &self.selection, &self.name)?;
24+
(field_impls, fields)
25+
} else if let Some(iface) = context.schema.interfaces.get(&self.on) {
26+
let field_impls =
27+
iface.field_impls_for_selection(context, &self.selection, &self.name)?;
28+
let fields =
29+
iface.response_fields_for_selection(context, &self.selection, &self.name)?;
30+
(field_impls, fields)
2131
} else {
2232
panic!(
2333
"fragment '{}' cannot operate on unknown type '{}'",
2434
self.name, self.on
2535
);
2636
};
27-
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;
28-
let fields = object.response_fields_for_selection(context, &self.selection, &self.name)?;
2937

3038
Ok(quote!{
3139
#derives

graphql_client_codegen/src/interfaces.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,22 @@ pub struct GqlInterface {
1919
}
2020

2121
impl GqlInterface {
22-
pub fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
22+
fn object_selection(&self, selection: &Selection) -> Selection {
23+
Selection(
24+
selection
25+
.0
26+
.iter()
27+
// Only keep what we can handle
28+
.filter(|f| match f {
29+
SelectionItem::Field(f) => f.name != "__typename",
30+
SelectionItem::FragmentSpread(_) => true,
31+
SelectionItem::InlineFragment(_) => false,
32+
}).map(|a| (*a).clone())
33+
.collect(),
34+
)
35+
}
36+
37+
pub(crate) fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
2338
GqlInterface {
2439
description: description.map(|d| d.to_owned()),
2540
name: name.into_owned(),
@@ -29,6 +44,35 @@ impl GqlInterface {
2944
}
3045
}
3146

47+
pub(crate) fn field_impls_for_selection(
48+
&self,
49+
context: &QueryContext,
50+
selection: &Selection,
51+
prefix: &str,
52+
) -> Result<Vec<TokenStream>, failure::Error> {
53+
::shared::field_impls_for_selection(
54+
&self.fields,
55+
context,
56+
&self.object_selection(selection),
57+
prefix,
58+
)
59+
}
60+
61+
pub(crate) fn response_fields_for_selection(
62+
&self,
63+
context: &QueryContext,
64+
selection: &Selection,
65+
prefix: &str,
66+
) -> Result<Vec<TokenStream>, failure::Error> {
67+
response_fields_for_selection(
68+
&self.name,
69+
&self.fields,
70+
context,
71+
&self.object_selection(selection),
72+
prefix,
73+
)
74+
}
75+
3276
pub(crate) fn response_for_selection(
3377
&self,
3478
query_context: &QueryContext,
@@ -42,19 +86,6 @@ impl GqlInterface {
4286
.extract_typename()
4387
.ok_or_else(|| format_err!("Missing __typename in selection for {}", prefix))?;
4488

45-
let object_selection = Selection(
46-
selection
47-
.0
48-
.iter()
49-
// Only keep what we can handle
50-
.filter(|f| match f {
51-
SelectionItem::Field(f) => f.name != "__typename",
52-
SelectionItem::FragmentSpread(_) => true,
53-
SelectionItem::InlineFragment(_) => false,
54-
}).map(|a| (*a).clone())
55-
.collect(),
56-
);
57-
5889
let union_selection = Selection(
5990
selection
6091
.0
@@ -67,16 +98,10 @@ impl GqlInterface {
6798
.collect(),
6899
);
69100

70-
let object_fields = response_fields_for_selection(
71-
&self.name,
72-
&self.fields,
73-
query_context,
74-
&object_selection,
75-
prefix,
76-
)?;
101+
let object_fields =
102+
self.response_fields_for_selection(query_context, &selection, prefix)?;
77103

78-
let object_children =
79-
field_impls_for_selection(&self.fields, query_context, &object_selection, prefix)?;
104+
let object_children = self.field_impls_for_selection(query_context, &selection, prefix)?;
80105
let (mut union_variants, union_children, used_variants) =
81106
union_variants(&union_selection, query_context, prefix)?;
82107

tests/interfaces.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ fn interface_not_on_everything_deserialization() {
8484
#[graphql(
8585
query_path = "tests/interfaces/interface_with_fragment_query.graphql",
8686
schema_path = "tests/interfaces/interface_schema.graphql",
87-
response_derives = "Debug",
87+
response_derives = "Debug,PartialEq",
8888
)]
8989
pub struct InterfaceWithFragmentQuery;
9090

@@ -93,6 +93,45 @@ const RESPONSE_FRAGMENT: &'static str =
9393

9494
#[test]
9595
fn fragment_in_interface() {
96+
use interface_with_fragment_query::*;
9697
let response_data: interface_with_fragment_query::ResponseData =
97-
serde_json::from_str(RESPONSE_FRAGMENT).unwrap();
98+
serde_json::from_str(RESPONSE_FRAGMENT).expect("RESPONSE_FRAGMENT did not deserialize");
99+
100+
assert_eq!(
101+
response_data,
102+
ResponseData {
103+
everything: Some(vec![
104+
RustMyQueryEverything {
105+
name: "Audrey Lorde".to_string(),
106+
public_status: PublicStatus {
107+
display_name: false,
108+
},
109+
on: RustMyQueryEverythingOn::Person(RustMyQueryEverythingOnPerson {
110+
birthday: Some("1934-02-18".to_string()),
111+
})
112+
},
113+
RustMyQueryEverything {
114+
name: "Laïka".to_string(),
115+
public_status: PublicStatus { display_name: true },
116+
on: RustMyQueryEverythingOn::Dog(RustMyQueryEverythingOnDog {
117+
is_good_dog: true,
118+
})
119+
},
120+
RustMyQueryEverything {
121+
name: "Mozilla".to_string(),
122+
public_status: PublicStatus {
123+
display_name: false
124+
},
125+
on: RustMyQueryEverythingOn::Organization,
126+
},
127+
RustMyQueryEverything {
128+
name: "Norbert".to_string(),
129+
public_status: PublicStatus { display_name: true },
130+
on: RustMyQueryEverythingOn::Dog(RustMyQueryEverythingOnDog {
131+
is_good_dog: true
132+
}),
133+
},
134+
])
135+
}
136+
)
98137
}
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
fragment PublicStatus on Named {
2-
displayName
2+
displayName
33
}
44

55
query MyQuery {
6-
everything {
7-
name
8-
__typename
9-
...PublicStatus
10-
... on Dog {
11-
isGoodDog
12-
}
13-
... on Person {
14-
birthday
15-
}
16-
... on Organization {
17-
industry
18-
}
6+
everything {
7+
name
8+
__typename
9+
...PublicStatus
10+
... on Dog {
11+
isGoodDog
1912
}
13+
... on Person {
14+
birthday
15+
}
16+
}
2017
}

tests/interfaces/interface_with_fragment_response.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
{
1616
"__typename": "Organization",
1717
"name": "Mozilla",
18-
"displayName": false,
19-
"industry": "OTHER"
18+
"displayName": false
2019
},
2120
{
2221
"__typename": "Dog",

0 commit comments

Comments
 (0)