From e7f7e7bff3b88da95fe05aa6bae6b2a76a43378c Mon Sep 17 00:00:00 2001 From: Tilman Roeder <dyed.green.info@gmail.com> Date: Fri, 2 Apr 2021 20:34:52 +0100 Subject: [PATCH] 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 --- .../juniper_tests/src/issue_407.rs | 139 ++++++++++++++++++ integration_tests/juniper_tests/src/lib.rs | 2 + juniper/src/types/async_await.rs | 51 +++++-- juniper/src/types/base.rs | 33 +++-- 4 files changed, 202 insertions(+), 23 deletions(-) create mode 100644 integration_tests/juniper_tests/src/issue_407.rs diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs new file mode 100644 index 00000000..6b4e79fb --- /dev/null +++ b/integration_tests/juniper_tests/src/issue_407.rs @@ -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); +} diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index d4f0e94b..ac40e335 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -15,4 +15,6 @@ mod issue_371; #[cfg(test)] mod issue_398; #[cfg(test)] +mod issue_407; +#[cfg(test)] mod issue_500; diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 3154db66..e1f6e6e1 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -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 { diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 086722cd..61d32ca8 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -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 {