From 5021ae80e1fe8ba680d019913fd341cb21605e25 Mon Sep 17 00:00:00 2001 From: bumblepie <luke@bumblepie.space> Date: Sun, 17 May 2020 19:43:37 +1200 Subject: [PATCH] Improve lookahead visibility for aliased fields (#662) * Improve lookahead visibility for aliased fields - Add a method to access the children of look ahead structs - Make the behaviour around accessing aliased lookahead fields more consistent * Deprecate old Lookahead methods for accessing child selections --- juniper/CHANGELOG.md | 4 ++ juniper/src/executor/look_ahead.rs | 103 +++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 8d6f2d09..35e6dabf 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -32,6 +32,8 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618). - Better error messages for all proc macros (see [#631](https://github.com/graphql-rust/juniper/pull/631) +- Improved lookahead visibility for aliased fields (see [#662](https://github.com/graphql-rust/juniper/pull/631)) + ## Breaking Changes - `juniper::graphiql` has moved to `juniper::http::graphiql` @@ -71,6 +73,8 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618). Rename `http::tests::HTTPIntegration` as `http::tests::HttpIntegration` and add support for `application/graphql` POST request. +- When using LookAheadMethods to access child selections, children are always found using their alias if it exists rather than their name (see [#662](https://github.com/graphql-rust/juniper/pull/631)). These methods are also deprecated in favour of the new `children` method. + # [[0.14.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.2) - Fix incorrect validation with non-executed operations [#455](https://github.com/graphql-rust/juniper/issues/455) diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index c9dbba6b..c5e05b6a 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -332,14 +332,19 @@ pub struct ConcreteLookAheadSelection<'a, S: 'a> { /// A set of common methods for `ConcreteLookAheadSelection` and `LookAheadSelection` pub trait LookAheadMethods<S> { - /// Get the name of the field represented by the current selection + /// Get the (potentially aliased) name of the field represented by the current selection fn field_name(&self) -> &str; /// Get the the child selection for a given field + /// If a child has an alias, it will only match if the alias matches `name` + #[deprecated(note = "please use `children` to access the child selections instead")] fn select_child(&self, name: &str) -> Option<&Self>; - /// Check if a given field exists + /// Check if a given child selection with a name exists + /// If a child has an alias, it will only match if the alias matches `name` + #[deprecated(note = "please use `children` to access the child selections instead")] fn has_child(&self, name: &str) -> bool { + #[allow(deprecated)] self.select_child(name).is_some() } @@ -357,8 +362,12 @@ pub trait LookAheadMethods<S> { self.arguments().iter().find(|a| a.name == name) } - /// Get the top level children for the current selection + /// Get the (possibly aliased) names of the top level children for the current selection + #[deprecated(note = "please use `children` to access the child selections instead")] fn child_names(&self) -> Vec<&str>; + + /// Get an iterator over the children for the current selection + fn children(&self) -> Vec<&Self>; } impl<'a, S> LookAheadMethods<S> for ConcreteLookAheadSelection<'a, S> { @@ -367,7 +376,7 @@ impl<'a, S> LookAheadMethods<S> for ConcreteLookAheadSelection<'a, S> { } fn select_child(&self, name: &str) -> Option<&Self> { - self.children.iter().find(|c| c.name == name) + self.children.iter().find(|c| c.field_name() == name) } fn arguments(&self) -> &[LookAheadArgument<S>] { @@ -375,10 +384,7 @@ impl<'a, S> LookAheadMethods<S> for ConcreteLookAheadSelection<'a, S> { } fn child_names(&self) -> Vec<&str> { - self.children - .iter() - .map(|c| c.alias.unwrap_or(c.name)) - .collect() + self.children.iter().map(|c| c.field_name()).collect() } fn has_arguments(&self) -> bool { @@ -388,6 +394,10 @@ impl<'a, S> LookAheadMethods<S> for ConcreteLookAheadSelection<'a, S> { fn has_children(&self) -> bool { !self.children.is_empty() } + + fn children(&self) -> Vec<&Self> { + self.children.iter().collect() + } } impl<'a, S> LookAheadMethods<S> for LookAheadSelection<'a, S> { @@ -398,7 +408,7 @@ impl<'a, S> LookAheadMethods<S> for LookAheadSelection<'a, S> { fn select_child(&self, name: &str) -> Option<&Self> { self.children .iter() - .find(|c| c.inner.name == name) + .find(|c| c.inner.field_name() == name) .map(|s| &s.inner) } @@ -407,10 +417,7 @@ impl<'a, S> LookAheadMethods<S> for LookAheadSelection<'a, S> { } fn child_names(&self) -> Vec<&str> { - self.children - .iter() - .map(|c| c.inner.alias.unwrap_or(c.inner.name)) - .collect() + self.children.iter().map(|c| c.inner.field_name()).collect() } fn has_arguments(&self) -> bool { @@ -420,6 +427,13 @@ impl<'a, S> LookAheadMethods<S> for LookAheadSelection<'a, S> { fn has_children(&self) -> bool { !self.children.is_empty() } + + fn children(&self) -> Vec<&Self> { + self.children + .iter() + .map(|child_selection| &child_selection.inner) + .collect() + } } #[cfg(test)] @@ -1290,6 +1304,7 @@ query Hero { } #[test] + #[allow(deprecated)] fn check_select_child() { let lookahead: LookAheadSelection<DefaultScalarValue> = LookAheadSelection { name: "hero", @@ -1441,12 +1456,14 @@ fragment heroFriendNames on Hero { } #[test] + #[allow(deprecated)] fn check_visitability() { let docs = parse_document_source::<DefaultScalarValue>( " query Hero { hero(episode: EMPIRE) { name + aliasedName: name friends { name } @@ -1474,22 +1491,56 @@ query Hero { assert_eq!(args[0].value(), &LookAheadValue::Enum("EMPIRE")); assert!(look_ahead.has_children()); - assert_eq!(look_ahead.child_names(), vec!["name", "friends"]); + assert_eq!( + look_ahead.child_names(), + vec!["name", "aliasedName", "friends"] + ); + let mut children = look_ahead.children().into_iter(); - let child0 = look_ahead.select_child("name").unwrap(); - assert_eq!(child0.field_name(), "name"); - assert!(!child0.has_arguments()); - assert!(!child0.has_children()); + let name_child = children.next().unwrap(); + assert!(look_ahead.has_child("name")); + assert_eq!(name_child, look_ahead.select_child("name").unwrap()); + assert_eq!(name_child.name, "name"); + assert_eq!(name_child.alias, None); + assert_eq!(name_child.field_name(), "name"); + assert!(!name_child.has_arguments()); + assert!(!name_child.has_children()); - let child1 = look_ahead.select_child("friends").unwrap(); - assert_eq!(child1.field_name(), "friends"); - assert!(!child1.has_arguments()); - assert!(child1.has_children()); - assert_eq!(child1.child_names(), vec!["name"]); + let aliased_name_child = children.next().unwrap(); + assert!(look_ahead.has_child("aliasedName")); + assert_eq!( + aliased_name_child, + look_ahead.select_child("aliasedName").unwrap() + ); + assert_eq!(aliased_name_child.name, "name"); + assert_eq!(aliased_name_child.alias, Some("aliasedName")); + assert_eq!(aliased_name_child.field_name(), "aliasedName"); + assert!(!aliased_name_child.has_arguments()); + assert!(!aliased_name_child.has_children()); - let child2 = child1.select_child("name").unwrap(); - assert!(!child2.has_arguments()); - assert!(!child2.has_children()); + let friends_child = children.next().unwrap(); + assert!(look_ahead.has_child("friends")); + assert_eq!(friends_child, look_ahead.select_child("friends").unwrap()); + assert_eq!(friends_child.name, "friends"); + assert_eq!(friends_child.alias, None); + assert_eq!(friends_child.field_name(), "friends"); + assert!(!friends_child.has_arguments()); + assert!(friends_child.has_children()); + assert_eq!(friends_child.child_names(), vec!["name"]); + + assert!(children.next().is_none()); + + let mut friends_children = friends_child.children().into_iter(); + let child = friends_children.next().unwrap(); + assert!(friends_child.has_child("name")); + assert_eq!(child, friends_child.select_child("name").unwrap()); + assert_eq!(child.name, "name"); + assert_eq!(child.alias, None); + assert_eq!(child.field_name(), "name"); + assert!(!child.has_arguments()); + assert!(!child.has_children()); + + assert!(friends_children.next().is_none()); } else { panic!("No Operation found"); }