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 {