Skip to content

Commit e7f7e7b

Browse files
authored
Fragment spreads in Interface types (#907)
* Add failing test * Fix fragment spread on interface * Cargo fmt * Add test and fix for sync version * Cargo fmt
1 parent 7a76be7 commit e7f7e7b

File tree

4 files changed

+202
-23
lines changed

4 files changed

+202
-23
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use juniper::*;
2+
3+
struct Query;
4+
5+
#[graphql_interface(for = [Human, Droid])]
6+
trait Character {
7+
fn id(&self) -> &str;
8+
}
9+
10+
#[derive(GraphQLObject)]
11+
#[graphql(impl = CharacterValue)]
12+
struct Human {
13+
id: String,
14+
name: String,
15+
}
16+
17+
#[graphql_interface]
18+
impl Character for Human {
19+
fn id(&self) -> &str {
20+
&self.id
21+
}
22+
}
23+
24+
#[derive(GraphQLObject)]
25+
#[graphql(impl = CharacterValue)]
26+
struct Droid {
27+
id: String,
28+
serial_number: String,
29+
}
30+
31+
#[graphql_interface]
32+
impl Character for Droid {
33+
fn id(&self) -> &str {
34+
&self.id
35+
}
36+
}
37+
38+
#[graphql_object]
39+
impl Query {
40+
fn characters() -> Vec<CharacterValue> {
41+
let human = Human {
42+
id: "1".to_string(),
43+
name: "Han Solo".to_string(),
44+
};
45+
let droid = Droid {
46+
id: "2".to_string(),
47+
serial_number: "234532545235".to_string(),
48+
};
49+
vec![Into::into(human), Into::into(droid)]
50+
}
51+
}
52+
53+
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
54+
55+
#[tokio::test]
56+
async fn test_fragments_in_interface() {
57+
let query = r#"
58+
query Query {
59+
characters {
60+
...HumanFragment
61+
...DroidFragment
62+
}
63+
}
64+
65+
fragment HumanFragment on Human {
66+
name
67+
}
68+
69+
fragment DroidFragment on Droid {
70+
serialNumber
71+
}
72+
"#;
73+
74+
let (_, errors) = juniper::execute(
75+
query,
76+
None,
77+
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
78+
&Variables::new(),
79+
&(),
80+
)
81+
.await
82+
.unwrap();
83+
assert_eq!(errors.len(), 0);
84+
85+
let (_, errors) = juniper::execute_sync(
86+
query,
87+
None,
88+
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
89+
&Variables::new(),
90+
&(),
91+
)
92+
.unwrap();
93+
assert_eq!(errors.len(), 0);
94+
}
95+
96+
#[tokio::test]
97+
async fn test_inline_fragments_in_interface() {
98+
let query = r#"
99+
query Query {
100+
characters {
101+
...on Human {
102+
...HumanFragment
103+
}
104+
...on Droid {
105+
...DroidFragment
106+
}
107+
}
108+
}
109+
110+
fragment HumanFragment on Human {
111+
name
112+
}
113+
114+
fragment DroidFragment on Droid {
115+
serialNumber
116+
}
117+
"#;
118+
119+
let (_, errors) = juniper::execute(
120+
query,
121+
None,
122+
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
123+
&Variables::new(),
124+
&(),
125+
)
126+
.await
127+
.unwrap();
128+
assert_eq!(errors.len(), 0);
129+
130+
let (_, errors) = juniper::execute_sync(
131+
query,
132+
None,
133+
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
134+
&Variables::new(),
135+
&(),
136+
)
137+
.unwrap();
138+
assert_eq!(errors.len(), 0);
139+
}

integration_tests/juniper_tests/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ mod issue_371;
1515
#[cfg(test)]
1616
mod issue_398;
1717
#[cfg(test)]
18+
mod issue_407;
19+
#[cfg(test)]
1820
mod issue_500;

juniper/src/types/async_await.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -288,24 +288,47 @@ where
288288
}
289289

290290
Selection::FragmentSpread(Spanning {
291-
item: ref spread, ..
291+
item: ref spread,
292+
start: ref start_pos,
293+
..
292294
}) => {
293295
if is_excluded(&spread.directives, executor.variables()) {
294296
continue;
295297
}
296-
async_values.push(AsyncValueFuture::FragmentSpread(async move {
297-
let fragment = &executor
298-
.fragment_by_name(spread.name.item)
299-
.expect("Fragment could not be found");
300-
let value = resolve_selection_set_into_async(
301-
instance,
302-
info,
303-
&fragment.selection_set[..],
304-
executor,
305-
)
306-
.await;
307-
AsyncValue::Nested(value)
308-
}));
298+
299+
let fragment = &executor
300+
.fragment_by_name(spread.name.item)
301+
.expect("Fragment could not be found");
302+
303+
let sub_exec = executor.type_sub_executor(
304+
Some(fragment.type_condition.item),
305+
Some(&fragment.selection_set[..]),
306+
);
307+
308+
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
309+
if fragment.type_condition.item == concrete_type_name {
310+
let sub_result = instance
311+
.resolve_into_type_async(
312+
info,
313+
fragment.type_condition.item,
314+
Some(&fragment.selection_set[..]),
315+
&sub_exec,
316+
)
317+
.await;
318+
319+
if let Ok(Value::Object(obj)) = sub_result {
320+
for (k, v) in obj {
321+
async_values.push(AsyncValueFuture::FragmentSpread(async move {
322+
AsyncValue::Field(AsyncField {
323+
name: k,
324+
value: Some(v),
325+
})
326+
}));
327+
}
328+
} else if let Err(e) = sub_result {
329+
sub_exec.push_error_at(e, *start_pos);
330+
}
331+
}
309332
}
310333

311334
Selection::InlineFragment(Spanning {

juniper/src/types/base.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,9 @@ where
497497
}
498498
}
499499
Selection::FragmentSpread(Spanning {
500-
item: ref spread, ..
500+
item: ref spread,
501+
start: ref start_pos,
502+
..
501503
}) => {
502504
if is_excluded(&spread.directives, executor.variables()) {
503505
continue;
@@ -507,14 +509,27 @@ where
507509
.fragment_by_name(spread.name.item)
508510
.expect("Fragment could not be found");
509511

510-
if !resolve_selection_set_into(
511-
instance,
512-
info,
513-
&fragment.selection_set[..],
514-
executor,
515-
result,
516-
) {
517-
return false;
512+
let sub_exec = executor.type_sub_executor(
513+
Some(fragment.type_condition.item),
514+
Some(&fragment.selection_set[..]),
515+
);
516+
517+
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
518+
if fragment.type_condition.item == concrete_type_name {
519+
let sub_result = instance.resolve_into_type(
520+
info,
521+
fragment.type_condition.item,
522+
Some(&fragment.selection_set[..]),
523+
&sub_exec,
524+
);
525+
526+
if let Ok(Value::Object(object)) = sub_result {
527+
for (k, v) in object {
528+
merge_key_into(result, &k, v);
529+
}
530+
} else if let Err(e) = sub_result {
531+
sub_exec.push_error_at(e, *start_pos);
532+
}
518533
}
519534
}
520535
Selection::InlineFragment(Spanning {

0 commit comments

Comments
 (0)