From 2c15ea798c1bd7df5bede24495fba541c1c84b2d Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 9 Dec 2020 19:26:59 +0100 Subject: [PATCH] Check type before resolving in inline fragments to fix panic when using inline fragments with interfaces (#816, #815) --- juniper/src/schema/schema.rs | 4 +++ juniper/src/tests/query_tests.rs | 34 ++++++++++++++++++++++++++ juniper/src/types/async_await.rs | 42 +++++++++++++++++--------------- juniper/src/types/base.rs | 28 ++++++++++++--------- 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 62f03cc51..261da6422 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -51,6 +51,10 @@ where QueryT::name(info) } + fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { + self.query_type.concrete_type_name(context, info) + } + fn resolve_field( &self, info: &Self::TypeInfo, diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs index d671c5bf6..32d79c1ca 100644 --- a/juniper/src/tests/query_tests.rs +++ b/juniper/src/tests/query_tests.rs @@ -1,6 +1,7 @@ use crate::{ ast::InputValue, executor::Variables, + graphql_value, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, @@ -731,3 +732,36 @@ async fn test_object_typename() { )) ); } + +#[tokio::test] +async fn interface_inline_fragment_friends() { + let doc = r#"{ + human(id: "1002") { + friends { + name + ... on Human { homePlanet } + ... on Droid { primaryFunction } + } + } + }"#; + let database = Database::new(); + let schema = RootNode::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + + assert_eq!( + crate::execute(doc, None, &schema, &Variables::new(), &database).await, + Ok(( + graphql_value!({"human": { + "friends": [ + {"name": "Luke Skywalker", "homePlanet": "Tatooine"}, + {"name": "Leia Organa", "homePlanet": "Alderaan"}, + {"name": "R2-D2", "primaryFunction": "Astromech"}, + ], + }}), + vec![], + )) + ); +} diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 395faf5ef..6979e15ab 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -323,26 +323,30 @@ where ); if let Some(ref type_condition) = fragment.type_condition { - let sub_result = instance - .resolve_into_type_async( - info, - 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::InlineFragment1(async move { - AsyncValue::Field(AsyncField { - name: k, - value: Some(v), - }) - })); + // Check whether the type matches the type condition. + let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); + if type_condition.item == concrete_type_name { + let sub_result = instance + .resolve_into_type_async( + info, + 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::InlineFragment1(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); } - } else if let Err(e) = sub_result { - sub_exec.push_error_at(e, *start_pos); } } else { async_values.push(AsyncValueFuture::InlineFragment2(async move { diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 1b395d440..d25f7441d 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -532,19 +532,23 @@ where ); if let Some(ref type_condition) = fragment.type_condition { - let sub_result = instance.resolve_into_type( - info, - 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); + // Check whether the type matches the type condition. + let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); + if type_condition.item == concrete_type_name { + let sub_result = instance.resolve_into_type( + info, + 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); } - } else if let Err(e) = sub_result { - sub_exec.push_error_at(e, *start_pos); } } else if !resolve_selection_set_into( instance,