From 5ae930bfc72c10b0a31afc044f99dd9652b5fd14 Mon Sep 17 00:00:00 2001 From: Tilman Roeder Date: Sun, 18 Apr 2021 00:29:08 +0100 Subject: [PATCH] Fixes Issue 914 (#915) * Add failing tests * Fix failing test * cargo fmt --- .../juniper_tests/src/issue_914.rs | 119 ++++++++++++++++++ integration_tests/juniper_tests/src/lib.rs | 2 + juniper/src/value/object.rs | 18 ++- 3 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 integration_tests/juniper_tests/src/issue_914.rs diff --git a/integration_tests/juniper_tests/src/issue_914.rs b/integration_tests/juniper_tests/src/issue_914.rs new file mode 100644 index 00000000..be15514f --- /dev/null +++ b/integration_tests/juniper_tests/src/issue_914.rs @@ -0,0 +1,119 @@ +use juniper::*; + +struct Query; + +#[derive(GraphQLObject)] +struct Foo { + bar: Bar, +} + +#[derive(GraphQLObject)] +struct Bar { + a: i32, + b: i32, + baz: Baz, +} + +#[derive(GraphQLObject)] +struct Baz { + c: i32, + d: i32, +} + +#[graphql_object] +impl Query { + fn foo() -> Foo { + let baz = Baz { c: 1, d: 2 }; + let bar = Bar { a: 1, b: 2, baz }; + Foo { bar } + } +} + +type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; + +#[tokio::test] +async fn test_fragments_with_nested_objects_dont_override_previous_selections() { + let query = r#" + query Query { + foo { + ...BarA + ...BarB + ...BazC + ...BazD + } + } + + fragment BarA on Foo { + bar { + a + } + } + + fragment BarB on Foo { + bar { + b + } + } + + fragment BazC on Foo { + bar { + baz { + c + } + } + } + + fragment BazD on Foo { + bar { + baz { + d + } + } + } + "#; + + let (async_value, errors) = juniper::execute( + query, + None, + &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .await + .unwrap(); + assert_eq!(errors.len(), 0); + + let (sync_value, errors) = juniper::execute_sync( + query, + None, + &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), + &Variables::new(), + &(), + ) + .unwrap(); + assert_eq!(errors.len(), 0); + + assert_eq!(async_value, sync_value); + + let bar = async_value + .as_object_value() + .unwrap() + .get_field_value("foo") + .unwrap() + .as_object_value() + .unwrap() + .get_field_value("bar") + .unwrap() + .as_object_value() + .unwrap(); + assert!(bar.contains_field("a"), "Field a should be selected"); + assert!(bar.contains_field("b"), "Field b should be selected"); + + let baz = bar + .get_field_value("baz") + .unwrap() + .as_object_value() + .unwrap(); + assert!(baz.contains_field("c"), "Field c should be selected"); + assert!(baz.contains_field("d"), "Field d should be selected"); +} diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index f6bfb683..ce6123d3 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -19,4 +19,6 @@ mod issue_407; #[cfg(test)] mod issue_500; #[cfg(test)] +mod issue_914; +#[cfg(test)] mod pre_parse; diff --git a/juniper/src/value/object.rs b/juniper/src/value/object.rs index 64299b8a..d4a6713a 100644 --- a/juniper/src/value/object.rs +++ b/juniper/src/value/object.rs @@ -28,14 +28,26 @@ impl Object { /// Add a new field with a value /// - /// If there is already a field with the same name the old value - /// is returned + /// If there is already a field for the given key + /// any both values are objects, they are merged. + /// + /// Otherwise the existing value is replaced and + /// returned. pub fn add_field(&mut self, k: K, value: Value) -> Option> where K: Into, for<'a> &'a str: PartialEq, { - self.key_value_list.insert(k.into(), value) + let key: String = k.into(); + match (value, self.key_value_list.get_mut(&key)) { + (Value::::Object(obj_val), Some(Value::::Object(existing_obj))) => { + for (key, val) in obj_val.into_iter() { + existing_obj.add_field::(key, val); + } + None + } + (non_obj_val, _) => self.key_value_list.insert(key, non_obj_val), + } } /// Check if the object already contains a field with the given name