From 6ff551fcb026b969bc3e1315dfa82431d322df94 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sat, 6 Apr 2019 08:33:37 -0700 Subject: [PATCH] Support fragments with nested types in lookahead Fixes https://github.com/graphql-rust/juniper/issues/335 --- juniper/src/executor/look_ahead.rs | 132 ++++++++++++++++++++++++----- juniper/src/executor/mod.rs | 49 ++++++----- 2 files changed, 137 insertions(+), 44 deletions(-) diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 17a144b1..ec6ce6b7 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -101,6 +101,21 @@ pub struct LookAheadSelection<'a, S: 'a> { pub(super) children: Vec>, } +impl<'a, S> Default for LookAheadSelection<'a, S> +where + S: ScalarValue, + &'a S: ScalarRefValue<'a>, +{ + fn default() -> Self { + LookAheadSelection { + name: "", + alias: None, + arguments: vec![], + children: vec![], + } + } +} + impl<'a, S> LookAheadSelection<'a, S> where S: ScalarValue, @@ -167,8 +182,8 @@ where s: &'a Selection<'a, S>, vars: &'a Variables, fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>, - ) -> LookAheadSelection<'a, S> { - Self::build_from_selection_with_parent(s, None, vars, fragments).unwrap() + ) -> Option> { + Self::build_from_selection_with_parent(s, None, vars, fragments) } fn build_from_selection_with_parent( @@ -229,21 +244,29 @@ where Some(ret) } } - Selection::FragmentSpread(ref fragment) if parent.is_some() => { + Selection::FragmentSpread(ref fragment) => { let include = Self::should_include(fragment.item.directives.as_ref(), vars); if !include { return None; } - let parent = parent.unwrap(); - let f = fragments.get(&fragment.item.name.item).unwrap(); - for c in f.selection_set.iter() { - let s = LookAheadSelection::build_from_selection_with_parent( - c, - Some(parent), - vars, - fragments, - ); - assert!(s.is_none()); + let f = fragments.get(&fragment.item.name.item).expect("a fragment"); + if let Some(parent) = parent { + for c in f.selection_set.iter() { + let s = LookAheadSelection::build_from_selection_with_parent( + c, + Some(parent), + vars, + fragments, + ); + assert!(s.is_none()); + } + } else { + for c in f.selection_set.iter() { + let s = LookAheadSelection::build_from_selection_with_parent( + c, None, vars, fragments, + ); + assert!(s.is_some()); + } } None } @@ -406,7 +429,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -459,7 +483,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: Some("custom_hero"), @@ -516,7 +541,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -597,7 +623,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -657,7 +684,8 @@ query Hero($episode: Episode) { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -718,7 +746,8 @@ fragment commonFields on Character { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -781,7 +810,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -838,7 +868,8 @@ query Hero { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -917,7 +948,8 @@ fragment comparisonFields on Character { &op.item.selection_set[0], &vars, &fragments, - ); + ) + .unwrap(); let expected = LookAheadSelection { name: "hero", alias: None, @@ -1069,6 +1101,7 @@ query Hero { &vars, &fragments, ) + .unwrap() .for_explicit_type("Human"); let expected = ConcreteLookAheadSelection { name: "hero", @@ -1191,4 +1224,59 @@ query Hero { ); } + #[test] + // https://github.com/graphql-rust/juniper/issues/335 + fn check_fragment_with_nesting() { + let docs = parse_document_source::( + " +query Hero { + hero { + ...heroFriendNames + } +} + +fragment heroFriendNames on Hero { + friends { name } +} +", + ) + .unwrap(); + let fragments = extract_fragments(&docs); + + if let ::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(); + let expected = LookAheadSelection { + name: "hero", + alias: None, + arguments: Vec::new(), + children: vec![ChildSelection { + inner: LookAheadSelection { + name: "friends", + alias: None, + arguments: Vec::new(), + children: vec![ChildSelection { + inner: LookAheadSelection { + name: "name", + alias: None, + arguments: Vec::new(), + children: Vec::new(), + }, + applies_for: Applies::All, + }], + }, + applies_for: Applies::All, + }], + }; + assert_eq!(look_ahead, expected); + } else { + panic!("No Operation found"); + } + } + } diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 03c923aa..5f6ed761 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -507,35 +507,40 @@ where }); } - /// Construct a lookahead selection for the current selection + /// Construct a lookahead selection for the current selection. /// - /// This allows to see the whole selection and preform operations - /// affecting the childs + /// This allows seeing the whole selection and perform operations + /// affecting the children. pub fn look_ahead(&'a self) -> LookAheadSelection<'a, S> { self.parent_selection_set .map(|p| { LookAheadSelection::build_from_selection(&p[0], self.variables, self.fragments) }) - .unwrap_or_else(|| LookAheadSelection { - name: self.current_type.innermost_concrete().name().unwrap_or(""), - alias: None, - arguments: Vec::new(), - children: self - .current_selection_set - .map(|s| { - s.iter() - .map(|s| ChildSelection { - inner: LookAheadSelection::build_from_selection( - s, - self.variables, - self.fragments, - ), - applies_for: Applies::All, - }) - .collect() - }) - .unwrap_or_else(Vec::new), + .filter(|s| s.is_some()) + .unwrap_or_else(|| { + Some(LookAheadSelection { + name: self.current_type.innermost_concrete().name().unwrap_or(""), + alias: None, + arguments: Vec::new(), + children: self + .current_selection_set + .map(|s| { + s.iter() + .map(|s| ChildSelection { + inner: LookAheadSelection::build_from_selection( + s, + self.variables, + self.fragments, + ) + .expect("a child selection"), + applies_for: Applies::All, + }) + .collect() + }) + .unwrap_or_else(Vec::new), + }) }) + .unwrap_or(LookAheadSelection::default()) } }