Merge pull request #574 from oeed/master

Changed Lookahead to support multiple fields
This commit is contained in:
Sunli 2021-07-15 10:00:38 +08:00 committed by GitHub
commit 1218664ed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 27 deletions

View File

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Add binary types to `ConstValue` and `Value`. [#569](https://github.com/async-graphql/async-graphql/issues/569) - Add binary types to `ConstValue` and `Value`. [#569](https://github.com/async-graphql/async-graphql/issues/569)
- Changed Lookahead to support multiple fields. [#574](https://github.com/async-graphql/async-graphql/issues/547)
- Attach custom HTTP headers to the response when an error occurs. [#572](https://github.com/async-graphql/async-graphql/issues/572) - Attach custom HTTP headers to the response when an error occurs. [#572](https://github.com/async-graphql/async-graphql/issues/572)

View File

@ -6,7 +6,7 @@ use crate::{Name, Positioned, SelectionField};
/// A selection performed by a query. /// A selection performed by a query.
pub struct Lookahead<'a> { pub struct Lookahead<'a> {
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>, fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
field: Option<&'a Field>, fields: Vec<&'a Field>,
} }
impl<'a> Lookahead<'a> { impl<'a> Lookahead<'a> {
@ -16,28 +16,31 @@ impl<'a> Lookahead<'a> {
) -> Self { ) -> Self {
Self { Self {
fragments, fragments,
field: Some(field), fields: vec![field],
} }
} }
/// Get the first subfield of the selection set with the specified name. This will ignore /// Get the field of the selection set with the specified name. This will ignore
/// aliases. /// aliases.
/// ///
/// For example, calling `.field("a")` on `{ a { b } }` will return a lookahead that /// For example, calling `.field("a")` on `{ a { b } }` will return a lookahead that
/// represents `{ b }`. /// represents `{ b }`.
pub fn field(&self, name: &str) -> Self { pub fn field(&self, name: &str) -> Self {
let mut fields = Vec::new();
for field in &self.fields {
filter(&mut fields, self.fragments, &field.selection_set.node, name)
}
Self { Self {
fragments: self.fragments, fragments: self.fragments,
field: self fields,
.field
.and_then(|field| find(self.fragments, &field.selection_set.node, name)),
} }
} }
/// Returns true if field exists otherwise return false. /// Returns true if field exists otherwise return false.
#[inline] #[inline]
pub fn exists(&self) -> bool { pub fn exists(&self) -> bool {
self.field.is_some() !self.fields.is_empty()
} }
} }
@ -45,34 +48,36 @@ impl<'a> From<SelectionField<'a>> for Lookahead<'a> {
fn from(selection_field: SelectionField<'a>) -> Self { fn from(selection_field: SelectionField<'a>) -> Self {
Lookahead { Lookahead {
fragments: selection_field.fragments, fragments: selection_field.fragments,
field: Some(selection_field.field), fields: vec![selection_field.field],
} }
} }
} }
fn find<'a>( fn filter<'a>(
fields: &mut Vec<&'a Field>,
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>, fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
selection_set: &'a SelectionSet, selection_set: &'a SelectionSet,
name: &str, name: &str,
) -> Option<&'a Field> { ) {
selection_set for item in &selection_set.items {
.items // doing this imperatively is a bit nasty, but using iterators would
.iter() // require a boxed return type (I believe) as its recusive
.find_map(|item| match &item.node { match &item.node {
Selection::Field(field) => { Selection::Field(field) => {
if field.node.name.node == name { if field.node.name.node == name {
Some(&field.node) fields.push(&field.node)
} else {
None
} }
} }
Selection::InlineFragment(fragment) => { Selection::InlineFragment(fragment) => {
find(fragments, &fragment.node.selection_set.node, name) filter(fields, fragments, &fragment.node.selection_set.node, name)
} }
Selection::FragmentSpread(spread) => fragments Selection::FragmentSpread(spread) => {
.get(&spread.node.fragment_name.node) if let Some(fragment) = fragments.get(&spread.node.fragment_name.node) {
.and_then(|fragment| find(fragments, &fragment.node.selection_set.node, name)), filter(fields, fragments, &fragment.node.selection_set.node, name)
}) }
}
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -104,12 +109,17 @@ mod tests {
if ctx.look_ahead().field("a").exists() { if ctx.look_ahead().field("a").exists() {
// This is a query like `obj { a }` // This is a query like `obj { a }`
assert_eq!(n, 1); assert_eq!(n, 1);
} else if ctx.look_ahead().field("detail").field("c").exists() { } else if ctx.look_ahead().field("detail").field("c").exists()
&& ctx.look_ahead().field("detail").field("d").exists()
{
// This is a query like `obj { detail { c } }` // This is a query like `obj { detail { c } }`
assert_eq!(n, 2); assert_eq!(n, 2);
} else if ctx.look_ahead().field("detail").field("c").exists() {
// This is a query like `obj { detail { c } }`
assert_eq!(n, 3);
} else { } else {
// This query doesn't have `a` // This query doesn't have `a`
assert_eq!(n, 3); assert_eq!(n, 4);
} }
MyObj { MyObj {
a: 0, a: 0,
@ -146,7 +156,7 @@ mod tests {
assert!(schema assert!(schema
.execute( .execute(
r#"{ r#"{
obj(n: 2) { obj(n: 3) {
detail { detail {
c c
} }
@ -159,7 +169,24 @@ mod tests {
assert!(schema assert!(schema
.execute( .execute(
r#"{ r#"{
obj(n: 3) { obj(n: 2) {
detail {
d
}
detail {
c
}
}
}"#,
)
.await
.is_ok());
assert!(schema
.execute(
r#"{
obj(n: 4) {
b b
} }
}"#, }"#,
@ -180,11 +207,30 @@ mod tests {
.await .await
.is_ok()); .is_ok());
assert!(schema
.execute(
r#"{
obj(n: 3) {
... {
detail {
c
}
}
}
}"#,
)
.await
.is_ok());
assert!(schema assert!(schema
.execute( .execute(
r#"{ r#"{
obj(n: 2) { obj(n: 2) {
... { ... {
detail {
d
}
detail { detail {
c c
} }
@ -213,7 +259,7 @@ mod tests {
assert!(schema assert!(schema
.execute( .execute(
r#"{ r#"{
obj(n: 2) { obj(n: 3) {
... A ... A
} }
} }
@ -226,5 +272,29 @@ mod tests {
) )
.await .await
.is_ok()); .is_ok());
assert!(schema
.execute(
r#"{
obj(n: 2) {
... A
... B
}
}
fragment A on MyObj {
detail {
d
}
}
fragment B on MyObj {
detail {
c
}
}"#,
)
.await
.is_ok());
} }
} }