Add Context::look_ahead
This commit is contained in:
parent
c4b619b98c
commit
ad8ba68d44
|
@ -1,8 +1,7 @@
|
|||
use crate::extensions::BoxExtension;
|
||||
use crate::parser::ast::{Directive, Field, SelectionSet};
|
||||
use crate::registry::Registry;
|
||||
use crate::{InputValueType, QueryError, Result, Schema, Type};
|
||||
use crate::{Pos, Positioned, Value};
|
||||
use crate::{InputValueType, Lookahead, Pos, Positioned, QueryError, Result, Schema, Type, Value};
|
||||
use async_graphql_parser::ast::Document;
|
||||
use async_graphql_parser::UploadValue;
|
||||
use fnv::FnvHashMap;
|
||||
|
@ -521,4 +520,48 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
|||
pub fn position(&self) -> Pos {
|
||||
self.pos
|
||||
}
|
||||
|
||||
/// Creates a uniform interface to inspect the forthcoming selections.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_graphql::*;
|
||||
///
|
||||
/// #[SimpleObject]
|
||||
/// struct Detail {
|
||||
/// c: i32,
|
||||
/// d: i32,
|
||||
/// }
|
||||
///
|
||||
/// #[SimpleObject]
|
||||
/// struct MyObj {
|
||||
/// a: i32,
|
||||
/// b: i32,
|
||||
/// #[field(ref)]
|
||||
/// detail: Detail,
|
||||
/// }
|
||||
///
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl Query {
|
||||
/// async fn obj(&self, ctx: &Context<'_>) -> MyObj {
|
||||
/// if ctx.look_ahead().field("a").exists() {
|
||||
/// // This is a query like `obj { a }`
|
||||
/// } else if ctx.look_ahead().field("detail").field("c").exists() {
|
||||
/// // This is a query like `obj { detail { c } }`
|
||||
/// } else {
|
||||
/// // This query doesn't have `a`
|
||||
/// }
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn look_ahead(&self) -> Lookahead {
|
||||
Lookahead {
|
||||
document: self.document,
|
||||
field: Some(&self.item.node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,18 +14,18 @@ pub trait Guard {
|
|||
/// An extension trait for `Guard`
|
||||
pub trait GuardExt: Guard + Sized {
|
||||
/// Merge the two guards.
|
||||
fn and<R: Guard>(self, other: R) -> GuardAnd<Self, R> {
|
||||
GuardAnd(self, other)
|
||||
fn and<R: Guard>(self, other: R) -> And<Self, R> {
|
||||
And(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Guard> GuardExt for T {}
|
||||
|
||||
/// Guard for `GuardExt::and`
|
||||
pub struct GuardAnd<A: Guard, B: Guard>(A, B);
|
||||
pub struct And<A: Guard, B: Guard>(A, B);
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<A: Guard + Send + Sync, B: Guard + Send + Sync> Guard for GuardAnd<A, B> {
|
||||
impl<A: Guard + Send + Sync, B: Guard + Send + Sync> Guard for And<A, B> {
|
||||
async fn check(&self, ctx: &Context<'_>) -> FieldResult<()> {
|
||||
self.0.check(ctx).await?;
|
||||
self.1.check(ctx).await
|
||||
|
|
|
@ -84,6 +84,7 @@ extern crate serde_derive;
|
|||
mod base;
|
||||
mod context;
|
||||
mod error;
|
||||
mod look_ahead;
|
||||
mod model;
|
||||
mod mutation_resolver;
|
||||
mod query;
|
||||
|
@ -120,6 +121,7 @@ pub use error::{
|
|||
Error, ErrorExtensions, FieldError, FieldResult, InputValueError, InputValueResult,
|
||||
ParseRequestError, QueryError, ResultExt,
|
||||
};
|
||||
pub use look_ahead::Lookahead;
|
||||
pub use parser::{Pos, Positioned, Value};
|
||||
pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse};
|
||||
pub use registry::CacheControl;
|
||||
|
|
208
src/look_ahead.rs
Normal file
208
src/look_ahead.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
use async_graphql_parser::ast::{Document, Field, Selection, SelectionSet};
|
||||
|
||||
/// A selection performed by a query
|
||||
pub struct Lookahead<'a> {
|
||||
pub(crate) document: &'a Document,
|
||||
pub(crate) field: Option<&'a Field>,
|
||||
}
|
||||
|
||||
impl<'a> Lookahead<'a> {
|
||||
/// Check if the specified field exists in the current selection.
|
||||
pub fn field(&self, name: &str) -> Lookahead {
|
||||
Lookahead {
|
||||
document: self.document,
|
||||
field: self
|
||||
.field
|
||||
.and_then(|field| find(self.document, &field.selection_set.node, name)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if field exists otherwise return false.
|
||||
#[inline]
|
||||
pub fn exists(&self) -> bool {
|
||||
self.field.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
fn find<'a>(
|
||||
document: &'a Document,
|
||||
selection_set: &'a SelectionSet,
|
||||
name: &str,
|
||||
) -> Option<&'a Field> {
|
||||
for item in &selection_set.items {
|
||||
match &item.node {
|
||||
Selection::Field(field) => {
|
||||
if field.name.node == name {
|
||||
return Some(&field.node);
|
||||
}
|
||||
}
|
||||
Selection::InlineFragment(inline_fragment) => {
|
||||
if let Some(field) = find(document, &inline_fragment.selection_set.node, name) {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
Selection::FragmentSpread(fragment_spread) => {
|
||||
if let Some(fragment) = document.fragments().get(fragment_spread.fragment_name.node)
|
||||
{
|
||||
if let Some(field) = find(document, &fragment.selection_set.node, name) {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_look_ahead() {
|
||||
#[SimpleObject(internal)]
|
||||
struct Detail {
|
||||
c: i32,
|
||||
d: i32,
|
||||
}
|
||||
|
||||
#[SimpleObject(internal)]
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
#[field(ref)]
|
||||
detail: Detail,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object(internal)]
|
||||
impl Query {
|
||||
async fn obj(&self, ctx: &Context<'_>, n: i32) -> MyObj {
|
||||
if ctx.look_ahead().field("a").exists() {
|
||||
// This is a query like `obj { a }`
|
||||
assert_eq!(n, 1);
|
||||
} else if ctx.look_ahead().field("detail").field("c").exists() {
|
||||
// This is a query like `obj { detail { c } }`
|
||||
assert_eq!(n, 2);
|
||||
} else {
|
||||
// This query doesn't have `a`
|
||||
assert_eq!(n, 3);
|
||||
}
|
||||
MyObj {
|
||||
a: 0,
|
||||
b: 0,
|
||||
detail: Detail { c: 0, d: 0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 1) {
|
||||
a
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 1) {
|
||||
k:a
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 2) {
|
||||
detail {
|
||||
c
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 3) {
|
||||
b
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 1) {
|
||||
... {
|
||||
a
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 2) {
|
||||
... {
|
||||
detail {
|
||||
c
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 1) {
|
||||
... A
|
||||
}
|
||||
}
|
||||
|
||||
fragment A on MyObj {
|
||||
a
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
schema
|
||||
.execute(
|
||||
r#"{
|
||||
obj(n: 2) {
|
||||
... A
|
||||
}
|
||||
}
|
||||
|
||||
fragment A on MyObj {
|
||||
detail {
|
||||
c
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ pub use string_validators::{Email, StringMaxLength, StringMinLength, MAC};
|
|||
///
|
||||
/// You can create your own input value validator by implementing this trait.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_graphql::*;
|
||||
/// use async_graphql::validators::{Email, MAC, IntRange};
|
||||
|
|
Loading…
Reference in New Issue
Block a user