Support fragments with nested types in lookahead

Fixes https://github.com/graphql-rust/juniper/issues/335
This commit is contained in:
Christian Legnitto 2019-04-06 08:33:37 -07:00 committed by theduke
parent 860aa8b419
commit 6ff551fcb0
2 changed files with 137 additions and 44 deletions

View file

@ -101,6 +101,21 @@ pub struct LookAheadSelection<'a, S: 'a> {
pub(super) children: Vec<ChildSelection<'a, S>>, pub(super) children: Vec<ChildSelection<'a, S>>,
} }
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> impl<'a, S> LookAheadSelection<'a, S>
where where
S: ScalarValue, S: ScalarValue,
@ -167,8 +182,8 @@ where
s: &'a Selection<'a, S>, s: &'a Selection<'a, S>,
vars: &'a Variables<S>, vars: &'a Variables<S>,
fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>, fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>,
) -> LookAheadSelection<'a, S> { ) -> Option<LookAheadSelection<'a, S>> {
Self::build_from_selection_with_parent(s, None, vars, fragments).unwrap() Self::build_from_selection_with_parent(s, None, vars, fragments)
} }
fn build_from_selection_with_parent( fn build_from_selection_with_parent(
@ -229,13 +244,13 @@ where
Some(ret) 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); let include = Self::should_include(fragment.item.directives.as_ref(), vars);
if !include { if !include {
return None; return None;
} }
let parent = parent.unwrap(); let f = fragments.get(&fragment.item.name.item).expect("a fragment");
let f = fragments.get(&fragment.item.name.item).unwrap(); if let Some(parent) = parent {
for c in f.selection_set.iter() { for c in f.selection_set.iter() {
let s = LookAheadSelection::build_from_selection_with_parent( let s = LookAheadSelection::build_from_selection_with_parent(
c, c,
@ -245,6 +260,14 @@ where
); );
assert!(s.is_none()); 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 None
} }
Selection::InlineFragment(ref inline) if parent.is_some() => { Selection::InlineFragment(ref inline) if parent.is_some() => {
@ -406,7 +429,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -459,7 +483,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: Some("custom_hero"), alias: Some("custom_hero"),
@ -516,7 +541,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -597,7 +623,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -657,7 +684,8 @@ query Hero($episode: Episode) {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -718,7 +746,8 @@ fragment commonFields on Character {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -781,7 +810,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -838,7 +868,8 @@ query Hero {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -917,7 +948,8 @@ fragment comparisonFields on Character {
&op.item.selection_set[0], &op.item.selection_set[0],
&vars, &vars,
&fragments, &fragments,
); )
.unwrap();
let expected = LookAheadSelection { let expected = LookAheadSelection {
name: "hero", name: "hero",
alias: None, alias: None,
@ -1069,6 +1101,7 @@ query Hero {
&vars, &vars,
&fragments, &fragments,
) )
.unwrap()
.for_explicit_type("Human"); .for_explicit_type("Human");
let expected = ConcreteLookAheadSelection { let expected = ConcreteLookAheadSelection {
name: "hero", 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::<DefaultScalarValue>(
"
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");
}
}
} }

View file

@ -507,16 +507,18 @@ 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 /// This allows seeing the whole selection and perform operations
/// affecting the childs /// affecting the children.
pub fn look_ahead(&'a self) -> LookAheadSelection<'a, S> { pub fn look_ahead(&'a self) -> LookAheadSelection<'a, S> {
self.parent_selection_set self.parent_selection_set
.map(|p| { .map(|p| {
LookAheadSelection::build_from_selection(&p[0], self.variables, self.fragments) LookAheadSelection::build_from_selection(&p[0], self.variables, self.fragments)
}) })
.unwrap_or_else(|| LookAheadSelection { .filter(|s| s.is_some())
.unwrap_or_else(|| {
Some(LookAheadSelection {
name: self.current_type.innermost_concrete().name().unwrap_or(""), name: self.current_type.innermost_concrete().name().unwrap_or(""),
alias: None, alias: None,
arguments: Vec::new(), arguments: Vec::new(),
@ -529,13 +531,16 @@ where
s, s,
self.variables, self.variables,
self.fragments, self.fragments,
), )
.expect("a child selection"),
applies_for: Applies::All, applies_for: Applies::All,
}) })
.collect() .collect()
}) })
.unwrap_or_else(Vec::new), .unwrap_or_else(Vec::new),
}) })
})
.unwrap_or(LookAheadSelection::default())
} }
} }