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>>,
}
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<S>,
fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>,
) -> LookAheadSelection<'a, S> {
Self::build_from_selection_with_parent(s, None, vars, fragments).unwrap()
) -> Option<LookAheadSelection<'a, S>> {
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::<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,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())
}
}