Skip to content

Commit 7597523

Browse files
authored
Allow spreading interface fragments on unions and other interfaces (#965, #798)
1 parent 88a7571 commit 7597523

File tree

6 files changed

+215
-8
lines changed

6 files changed

+215
-8
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use juniper::{
2+
graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription,
3+
GraphQLObject, GraphQLUnion, RootNode, Variables,
4+
};
5+
6+
#[graphql_interface(for = [Human, Droid])]
7+
trait Character {
8+
fn id(&self) -> &str;
9+
}
10+
11+
#[derive(GraphQLObject)]
12+
#[graphql(impl = CharacterValue)]
13+
struct Human {
14+
id: String,
15+
home_planet: String,
16+
}
17+
18+
#[graphql_interface]
19+
impl Character for Human {
20+
fn id(&self) -> &str {
21+
&self.id
22+
}
23+
}
24+
25+
#[derive(GraphQLObject)]
26+
#[graphql(impl = CharacterValue)]
27+
struct Droid {
28+
id: String,
29+
primary_function: String,
30+
}
31+
32+
#[graphql_interface]
33+
impl Character for Droid {
34+
fn id(&self) -> &str {
35+
&self.id
36+
}
37+
}
38+
39+
#[derive(GraphQLUnion)]
40+
enum FieldResult {
41+
Human(Human),
42+
Droid(Droid),
43+
}
44+
45+
#[derive(Clone, Copy)]
46+
enum Query {
47+
Human,
48+
Droid,
49+
}
50+
51+
#[graphql_object]
52+
impl Query {
53+
fn field(&self) -> FieldResult {
54+
match self {
55+
Self::Human => FieldResult::Human(Human {
56+
id: "human-32".to_owned(),
57+
home_planet: "earth".to_owned(),
58+
}),
59+
Self::Droid => FieldResult::Droid(Droid {
60+
id: "droid-99".to_owned(),
61+
primary_function: "run".to_owned(),
62+
}),
63+
}
64+
}
65+
}
66+
67+
type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
68+
69+
#[tokio::test]
70+
async fn test_interface_inline_fragment_on_union() {
71+
let query = r#"
72+
query Query {
73+
field {
74+
__typename
75+
... on Character {
76+
id
77+
}
78+
... on Human {
79+
homePlanet
80+
}
81+
... on Droid {
82+
primaryFunction
83+
}
84+
}
85+
}
86+
"#;
87+
88+
let (res, errors) = juniper::execute(
89+
query,
90+
None,
91+
&Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()),
92+
&Variables::new(),
93+
&(),
94+
)
95+
.await
96+
.unwrap();
97+
98+
assert_eq!(errors.len(), 0);
99+
assert_eq!(
100+
res,
101+
graphql_value!({
102+
"field": {
103+
"__typename": "Human",
104+
"id": "human-32",
105+
"homePlanet": "earth",
106+
},
107+
}),
108+
);
109+
110+
let (res, errors) = juniper::execute_sync(
111+
query,
112+
None,
113+
&Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()),
114+
&Variables::new(),
115+
&(),
116+
)
117+
.unwrap();
118+
119+
assert_eq!(errors.len(), 0);
120+
assert_eq!(
121+
res,
122+
graphql_value!({
123+
"field": {
124+
"__typename": "Droid",
125+
"id": "droid-99",
126+
"primaryFunction": "run",
127+
},
128+
}),
129+
);
130+
}
131+
132+
#[tokio::test]
133+
async fn test_interface_fragment_on_union() {
134+
let query = r#"
135+
query Query {
136+
field {
137+
__typename
138+
... CharacterFragment
139+
... on Human {
140+
homePlanet
141+
}
142+
... on Droid {
143+
primaryFunction
144+
}
145+
}
146+
}
147+
148+
fragment CharacterFragment on Character {
149+
id
150+
}
151+
"#;
152+
153+
let (res, errors) = juniper::execute(
154+
query,
155+
None,
156+
&Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()),
157+
&Variables::new(),
158+
&(),
159+
)
160+
.await
161+
.unwrap();
162+
163+
assert_eq!(errors.len(), 0);
164+
assert_eq!(
165+
res,
166+
graphql_value!({
167+
"field": {
168+
"__typename": "Human",
169+
"id": "human-32",
170+
"homePlanet": "earth",
171+
},
172+
}),
173+
);
174+
175+
let (res, errors) = juniper::execute_sync(
176+
query,
177+
None,
178+
&Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()),
179+
&Variables::new(),
180+
&(),
181+
)
182+
.unwrap();
183+
184+
assert_eq!(errors.len(), 0);
185+
assert_eq!(
186+
res,
187+
graphql_value!({
188+
"field": {
189+
"__typename": "Droid",
190+
"id": "droid-99",
191+
"primaryFunction": "run",
192+
},
193+
}),
194+
);
195+
}

integration_tests/juniper_tests/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ mod issue_407;
1919
#[cfg(test)]
2020
mod issue_500;
2121
#[cfg(test)]
22+
mod issue_798;
23+
#[cfg(test)]
2224
mod issue_914;
2325
#[cfg(test)]
2426
mod issue_922;

juniper/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# master
22

3-
- No changes yet
3+
- Allow spreading interface fragments on unions and other interfaces ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798))
44

55
# [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7)
66

juniper/src/tests/subscriptions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Schema =
3333
RootNode<'static, MyQuery, EmptyMutation<MyContext>, MySubscription, DefaultScalarValue>;
3434

3535
fn run<O>(f: impl std::future::Future<Output = O>) -> O {
36-
let mut rt = tokio::runtime::Runtime::new().unwrap();
36+
let rt = tokio::runtime::Runtime::new().unwrap();
3737

3838
rt.block_on(f)
3939
}

juniper/src/types/async_await.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ where
307307

308308
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
309309
let type_name = instance.type_name(info);
310-
if fragment.type_condition.item == concrete_type_name
310+
if executor
311+
.schema()
312+
.is_named_subtype(&concrete_type_name, &fragment.type_condition.item)
311313
|| Some(fragment.type_condition.item) == type_name
312314
{
313315
let sub_result = instance
@@ -351,11 +353,14 @@ where
351353
if let Some(ref type_condition) = fragment.type_condition {
352354
// Check whether the type matches the type condition.
353355
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
354-
if type_condition.item == concrete_type_name {
356+
if executor
357+
.schema()
358+
.is_named_subtype(&concrete_type_name, &type_condition.item)
359+
{
355360
let sub_result = instance
356361
.resolve_into_type_async(
357362
info,
358-
type_condition.item,
363+
&concrete_type_name,
359364
Some(&fragment.selection_set[..]),
360365
&sub_exec,
361366
)

juniper/src/types/base.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,9 @@ where
516516

517517
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
518518
let type_name = instance.type_name(info);
519-
if fragment.type_condition.item == concrete_type_name
519+
if executor
520+
.schema()
521+
.is_named_subtype(&concrete_type_name, &fragment.type_condition.item)
520522
|| Some(fragment.type_condition.item) == type_name
521523
{
522524
let sub_result = instance.resolve_into_type(
@@ -552,10 +554,13 @@ where
552554
if let Some(ref type_condition) = fragment.type_condition {
553555
// Check whether the type matches the type condition.
554556
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
555-
if type_condition.item == concrete_type_name {
557+
if executor
558+
.schema()
559+
.is_named_subtype(&concrete_type_name, &type_condition.item)
560+
{
556561
let sub_result = instance.resolve_into_type(
557562
info,
558-
type_condition.item,
563+
&concrete_type_name,
559564
Some(&fragment.selection_set[..]),
560565
&sub_exec,
561566
);

0 commit comments

Comments
 (0)