From 5be66654a961241839bd9f1296f916d1dd08aaa8 Mon Sep 17 00:00:00 2001 From: James Harton <59449+jimsynz@users.noreply.github.com> Date: Mon, 30 Sep 2019 14:00:45 +1300 Subject: [PATCH] Improve visitability of lookahead types. (#431) I've added methods which allow Juniper users to visit all nodes of a lookahead tree so that they can be used for query generation. --- juniper/CHANGELOG.md | 11 ++-- juniper/src/executor/look_ahead.rs | 99 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index ad248a21..31f9c44a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,10 +1,11 @@ # master -- Add ability to parse 'subscription' +- Improve lookahead visitability. +- Add ability to parse 'subscription'. # [[0.13.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.13.1) -- Fix a regression when using lookaheads with fragments containing nested types [#404](https://github.com/graphql-rust/juniper/pull/404) +- Fix a regression when using lookaheads with fragments containing nested types [#404](https://github.com/graphql-rust/juniper/pull/404) - Allow `mut` arguments for resolver functions in `#[object]` macros [#402](https://github.com/graphql-rust/juniper/pull/402) @@ -15,7 +16,7 @@ See [#345](https://github.com/graphql-rust/juniper/pull/345). The newtype pattern can now be used with the `GraphQLScalarValue` custom derive -to easily implement custom scalar values that just wrap another scalar, +to easily implement custom scalar values that just wrap another scalar, similar to serdes `#[serde(transparent)]` functionality. Example: @@ -34,7 +35,7 @@ struct UserId(i32); ### object macro -The `graphql_object!` macro is deprecated and will be removed in the future. +The `graphql_object!` macro is deprecated and will be removed in the future. It is replaced by the new [object](https://docs.rs/juniper/latest/juniper/macro.object.html) procedural macro. [#333](https://github.com/graphql-rust/juniper/pull/333) @@ -53,7 +54,7 @@ This should not have any impact on your code, since juniper already was 2018 com - The `GraphQLType` impl for () was removed to improve compile time safefty. [#355](https://github.com/graphql-rust/juniper/pull/355) - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - Added built-in support for the canonical schema introspection query via - `juniper::introspect()`. + `juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307) - Fix introspection query validity The DirectiveLocation::InlineFragment had an invalid literal value, diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index e4e4fe4f..3c8fd7e4 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -88,6 +88,11 @@ where } } + /// The argument's name + pub fn name(&'a self) -> &str { + &self.name + } + /// The value of the argument pub fn value(&'a self) -> &LookAheadValue<'a, S> { &self.value @@ -347,6 +352,12 @@ pub trait LookAheadMethods { self.select_child(name).is_some() } + /// Does the current node have any arguments? + fn has_arguments(&self) -> bool; + + /// Does the current node have any children? + fn has_children(&self) -> bool; + /// Get the top level arguments for the current selection fn arguments(&self) -> &[LookAheadArgument]; @@ -354,6 +365,9 @@ pub trait LookAheadMethods { fn argument(&self, name: &str) -> Option<&LookAheadArgument> { self.arguments().iter().find(|a| a.name == name) } + + /// Get the top level children for the current selection + fn child_names(&self) -> Vec<&str>; } impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { @@ -368,6 +382,21 @@ impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { fn arguments(&self) -> &[LookAheadArgument] { &self.arguments } + + fn child_names(&self) -> Vec<&str> { + self.children + .iter() + .map(|c| c.alias.unwrap_or(c.name)) + .collect() + } + + fn has_arguments(&self) -> bool { + !self.arguments.is_empty() + } + + fn has_children(&self) -> bool { + !self.children.is_empty() + } } impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { @@ -385,6 +414,21 @@ impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { fn arguments(&self) -> &[LookAheadArgument] { &self.arguments } + + fn child_names(&self) -> Vec<&str> { + self.children + .iter() + .map(|c| c.inner.alias.unwrap_or(c.inner.name)) + .collect() + } + + fn has_arguments(&self) -> bool { + !self.arguments.is_empty() + } + + fn has_children(&self) -> bool { + !self.children.is_empty() + } } #[cfg(test)] @@ -1399,4 +1443,59 @@ fragment heroFriendNames on Hero { panic!("No Operation found"); } } + + #[test] + fn check_visitability() { + let docs = parse_document_source::( + " +query Hero { + hero(episode: EMPIRE) { + name + friends { + name + } + } +} + ", + ) + .unwrap(); + let fragments = extract_fragments(&docs); + + if let crate::ast::Definition::Operation(ref op) = docs[0] { + let vars = Variables::default(); + let look_ahead = LookAheadSelection::build_from_selection( + &op.item.selection_set[0], + &vars, + &fragments, + ) + .unwrap(); + + assert_eq!(look_ahead.field_name(), "hero"); + + assert!(look_ahead.has_arguments()); + let args = look_ahead.arguments(); + assert_eq!(args[0].name(), "episode"); + assert_eq!(args[0].value(), &LookAheadValue::Enum("EMPIRE")); + + assert!(look_ahead.has_children()); + assert_eq!(look_ahead.child_names(), vec!["name", "friends"]); + + let child0 = look_ahead.select_child("name").unwrap(); + assert_eq!(child0.field_name(), "name"); + assert!(!child0.has_arguments()); + assert!(!child0.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 child2 = child1.select_child("name").unwrap(); + assert!(!child2.has_arguments()); + assert!(!child2.has_children()); + } else { + panic!("No Operation found"); + } + } }