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:
parent
7a76be7407
commit
e7f7e7bff3
4 changed files with 202 additions and 23 deletions
139
integration_tests/juniper_tests/src/issue_407.rs
Normal file
139
integration_tests/juniper_tests/src/issue_407.rs
Normal 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);
|
||||||
|
}
|
|
@ -15,4 +15,6 @@ mod issue_371;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod issue_398;
|
mod issue_398;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod issue_407;
|
||||||
|
#[cfg(test)]
|
||||||
mod issue_500;
|
mod issue_500;
|
||||||
|
|
|
@ -288,24 +288,47 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
Selection::FragmentSpread(Spanning {
|
Selection::FragmentSpread(Spanning {
|
||||||
item: ref spread, ..
|
item: ref spread,
|
||||||
|
start: ref start_pos,
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if is_excluded(&spread.directives, executor.variables()) {
|
if is_excluded(&spread.directives, executor.variables()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
async_values.push(AsyncValueFuture::FragmentSpread(async move {
|
|
||||||
let fragment = &executor
|
let fragment = &executor
|
||||||
.fragment_by_name(spread.name.item)
|
.fragment_by_name(spread.name.item)
|
||||||
.expect("Fragment could not be found");
|
.expect("Fragment could not be found");
|
||||||
let value = resolve_selection_set_into_async(
|
|
||||||
instance,
|
let sub_exec = executor.type_sub_executor(
|
||||||
info,
|
Some(fragment.type_condition.item),
|
||||||
&fragment.selection_set[..],
|
Some(&fragment.selection_set[..]),
|
||||||
executor,
|
);
|
||||||
)
|
|
||||||
.await;
|
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
|
||||||
AsyncValue::Nested(value)
|
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 {
|
Selection::InlineFragment(Spanning {
|
||||||
|
|
|
@ -497,7 +497,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Selection::FragmentSpread(Spanning {
|
Selection::FragmentSpread(Spanning {
|
||||||
item: ref spread, ..
|
item: ref spread,
|
||||||
|
start: ref start_pos,
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if is_excluded(&spread.directives, executor.variables()) {
|
if is_excluded(&spread.directives, executor.variables()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -507,14 +509,27 @@ where
|
||||||
.fragment_by_name(spread.name.item)
|
.fragment_by_name(spread.name.item)
|
||||||
.expect("Fragment could not be found");
|
.expect("Fragment could not be found");
|
||||||
|
|
||||||
if !resolve_selection_set_into(
|
let sub_exec = executor.type_sub_executor(
|
||||||
instance,
|
Some(fragment.type_condition.item),
|
||||||
info,
|
Some(&fragment.selection_set[..]),
|
||||||
&fragment.selection_set[..],
|
);
|
||||||
executor,
|
|
||||||
result,
|
let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info);
|
||||||
) {
|
if fragment.type_condition.item == concrete_type_name {
|
||||||
return false;
|
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 {
|
Selection::InlineFragment(Spanning {
|
||||||
|
|
Loading…
Add table
Reference in a new issue