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)]
|
||||
mod issue_398;
|
||||
#[cfg(test)]
|
||||
mod issue_407;
|
||||
#[cfg(test)]
|
||||
mod issue_500;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue