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
This commit is contained in:
Tilman Roeder 2021-04-02 20:34:52 +01:00 committed by GitHub
parent 7a76be7407
commit e7f7e7bff3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 202 additions and 23 deletions

View file

@ -0,0 +1,139 @@
use juniper::*;
struct Query;
#[graphql_interface(for = [Human, Droid])]
trait Character {
fn id(&self) -> &str;
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Human {
id: String,
name: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
id: String,
serial_number: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
#[graphql_object]
impl Query {
fn characters() -> Vec<CharacterValue> {
let human = Human {
id: "1".to_string(),
name: "Han Solo".to_string(),
};
let droid = Droid {
id: "2".to_string(),
serial_number: "234532545235".to_string(),
};
vec![Into::into(human), Into::into(droid)]
}
}
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
#[tokio::test]
async fn test_fragments_in_interface() {
let query = r#"
query Query {
characters {
...HumanFragment
...DroidFragment
}
}
fragment HumanFragment on Human {
name
}
fragment DroidFragment on Droid {
serialNumber
}
"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await
.unwrap();
assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
assert_eq!(errors.len(), 0);
}
#[tokio::test]
async fn test_inline_fragments_in_interface() {
let query = r#"
query Query {
characters {
...on Human {
...HumanFragment
}
...on Droid {
...DroidFragment
}
}
}
fragment HumanFragment on Human {
name
}
fragment DroidFragment on Droid {
serialNumber
}
"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await
.unwrap();
assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
assert_eq!(errors.len(), 0);
}

View file

@ -15,4 +15,6 @@ mod issue_371;
#[cfg(test)]
mod issue_398;
#[cfg(test)]
mod issue_407;
#[cfg(test)]
mod issue_500;

View file

@ -288,24 +288,47 @@ where
}
Selection::FragmentSpread(Spanning {
item: ref spread, ..
item: ref spread,
start: ref start_pos,
..
}) => {
if is_excluded(&spread.directives, executor.variables()) {
continue;
}
async_values.push(AsyncValueFuture::FragmentSpread(async move {
let fragment = &executor
.fragment_by_name(spread.name.item)
.expect("Fragment could not be found");
let value = resolve_selection_set_into_async(
instance,
info,
&fragment.selection_set[..],
executor,
)
.await;
AsyncValue::Nested(value)
}));
let fragment = &executor
.fragment_by_name(spread.name.item)
.expect("Fragment could not be found");
let sub_exec = executor.type_sub_executor(
Some(fragment.type_condition.item),
Some(&fragment.selection_set[..]),
);
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
if fragment.type_condition.item == concrete_type_name {
let sub_result = instance
.resolve_into_type_async(
info,
fragment.type_condition.item,
Some(&fragment.selection_set[..]),
&sub_exec,
)
.await;
if let Ok(Value::Object(obj)) = sub_result {
for (k, v) in obj {
async_values.push(AsyncValueFuture::FragmentSpread(async move {
AsyncValue::Field(AsyncField {
name: k,
value: Some(v),
})
}));
}
} else if let Err(e) = sub_result {
sub_exec.push_error_at(e, *start_pos);
}
}
}
Selection::InlineFragment(Spanning {

View file

@ -497,7 +497,9 @@ where
}
}
Selection::FragmentSpread(Spanning {
item: ref spread, ..
item: ref spread,
start: ref start_pos,
..
}) => {
if is_excluded(&spread.directives, executor.variables()) {
continue;
@ -507,14 +509,27 @@ where
.fragment_by_name(spread.name.item)
.expect("Fragment could not be found");
if !resolve_selection_set_into(
instance,
info,
&fragment.selection_set[..],
executor,
result,
) {
return false;
let sub_exec = executor.type_sub_executor(
Some(fragment.type_condition.item),
Some(&fragment.selection_set[..]),
);
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
if fragment.type_condition.item == concrete_type_name {
let sub_result = instance.resolve_into_type(
info,
fragment.type_condition.item,
Some(&fragment.selection_set[..]),
&sub_exec,
);
if let Ok(Value::Object(object)) = sub_result {
for (k, v) in object {
merge_key_into(result, &k, v);
}
} else if let Err(e) = sub_result {
sub_exec.push_error_at(e, *start_pos);
}
}
}
Selection::InlineFragment(Spanning {