From 7597523720b24e9dda3a4e0c94067b852f02465c Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Mon, 19 Jul 2021 14:06:47 +0300 Subject: [PATCH] Allow spreading interface fragments on unions and other interfaces (#965, #798) --- .../juniper_tests/src/issue_798.rs | 195 ++++++++++++++++++ integration_tests/juniper_tests/src/lib.rs | 2 + juniper/CHANGELOG.md | 2 +- juniper/src/tests/subscriptions.rs | 2 +- juniper/src/types/async_await.rs | 11 +- juniper/src/types/base.rs | 11 +- 6 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 integration_tests/juniper_tests/src/issue_798.rs diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs new file mode 100644 index 00000000..f3ec4d70 --- /dev/null +++ b/integration_tests/juniper_tests/src/issue_798.rs @@ -0,0 +1,195 @@ +use juniper::{ + graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription, + GraphQLObject, GraphQLUnion, RootNode, Variables, +}; + +#[graphql_interface(for = [Human, Droid])] +trait Character { + fn id(&self) -> &str; +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +struct Human { + id: String, + home_planet: String, +} + +#[graphql_interface] +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +struct Droid { + id: String, + primary_function: String, +} + +#[graphql_interface] +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } +} + +#[derive(GraphQLUnion)] +enum FieldResult { + Human(Human), + Droid(Droid), +} + +#[derive(Clone, Copy)] +enum Query { + Human, + Droid, +} + +#[graphql_object] +impl Query { + fn field(&self) -> FieldResult { + match self { + Self::Human => FieldResult::Human(Human { + id: "human-32".to_owned(), + home_planet: "earth".to_owned(), + }), + Self::Droid => FieldResult::Droid(Droid { + id: "droid-99".to_owned(), + primary_function: "run".to_owned(), + }), + } + } +} + +type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; + +#[tokio::test] +async fn test_interface_inline_fragment_on_union() { + let query = r#" + query Query { + field { + __typename + ... on Character { + id + } + ... on Human { + homePlanet + } + ... on Droid { + primaryFunction + } + } + } + "#; + + let (res, errors) = juniper::execute( + query, + None, + &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .await + .unwrap(); + + assert_eq!(errors.len(), 0); + assert_eq!( + res, + graphql_value!({ + "field": { + "__typename": "Human", + "id": "human-32", + "homePlanet": "earth", + }, + }), + ); + + let (res, errors) = juniper::execute_sync( + query, + None, + &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .unwrap(); + + assert_eq!(errors.len(), 0); + assert_eq!( + res, + graphql_value!({ + "field": { + "__typename": "Droid", + "id": "droid-99", + "primaryFunction": "run", + }, + }), + ); +} + +#[tokio::test] +async fn test_interface_fragment_on_union() { + let query = r#" + query Query { + field { + __typename + ... CharacterFragment + ... on Human { + homePlanet + } + ... on Droid { + primaryFunction + } + } + } + + fragment CharacterFragment on Character { + id + } + "#; + + let (res, errors) = juniper::execute( + query, + None, + &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .await + .unwrap(); + + assert_eq!(errors.len(), 0); + assert_eq!( + res, + graphql_value!({ + "field": { + "__typename": "Human", + "id": "human-32", + "homePlanet": "earth", + }, + }), + ); + + let (res, errors) = juniper::execute_sync( + query, + None, + &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .unwrap(); + + assert_eq!(errors.len(), 0); + assert_eq!( + res, + graphql_value!({ + "field": { + "__typename": "Droid", + "id": "droid-99", + "primaryFunction": "run", + }, + }), + ); +} diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index 5592c146..f94c4d72 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -19,6 +19,8 @@ mod issue_407; #[cfg(test)] mod issue_500; #[cfg(test)] +mod issue_798; +#[cfg(test)] mod issue_914; #[cfg(test)] mod issue_922; diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 4b08aa5e..2741f350 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,6 +1,6 @@ # master -- No changes yet +- Allow spreading interface fragments on unions and other interfaces ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798)) # [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7) diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs index ec0d3b66..66d118fc 100644 --- a/juniper/src/tests/subscriptions.rs +++ b/juniper/src/tests/subscriptions.rs @@ -33,7 +33,7 @@ type Schema = RootNode<'static, MyQuery, EmptyMutation, MySubscription, DefaultScalarValue>; fn run(f: impl std::future::Future) -> O { - let mut rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(f) } diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index ca253c1a..21862771 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -307,7 +307,9 @@ where let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); let type_name = instance.type_name(info); - if fragment.type_condition.item == concrete_type_name + if executor + .schema() + .is_named_subtype(&concrete_type_name, &fragment.type_condition.item) || Some(fragment.type_condition.item) == type_name { let sub_result = instance @@ -351,11 +353,14 @@ where if let Some(ref type_condition) = fragment.type_condition { // 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 { + if executor + .schema() + .is_named_subtype(&concrete_type_name, &type_condition.item) + { let sub_result = instance .resolve_into_type_async( info, - type_condition.item, + &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, ) diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 2102925a..1b351a21 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -516,7 +516,9 @@ where let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); let type_name = instance.type_name(info); - if fragment.type_condition.item == concrete_type_name + if executor + .schema() + .is_named_subtype(&concrete_type_name, &fragment.type_condition.item) || Some(fragment.type_condition.item) == type_name { let sub_result = instance.resolve_into_type( @@ -552,10 +554,13 @@ where if let Some(ref type_condition) = fragment.type_condition { // 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 { + if executor + .schema() + .is_named_subtype(&concrete_type_name, &type_condition.item) + { let sub_result = instance.resolve_into_type( info, - type_condition.item, + &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, );