Add defer tests

This commit is contained in:
sunli 2020-05-20 08:18:28 +08:00
parent 75bfba057a
commit 6f924efcf4
26 changed files with 336 additions and 167 deletions

View File

@ -163,7 +163,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
#[#crate_name::async_trait::async_trait]
impl #crate_name::OutputValueType for #ident {
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::EnumType::resolve_enum(self)
}
}

View File

@ -245,7 +245,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
if ctx.name.node == #name {
#(#get_params)*
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position()).await;
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await;
}
});
}
@ -325,7 +325,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -166,7 +166,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
if let (#(#key_pat),*) = (#(#key_getter),*) {
#guard
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return #crate_name::OutputValueType::resolve(&#do_find, &ctx_obj, ctx.position()).await;
return #crate_name::OutputValueType::resolve(&#do_find, &ctx_obj, ctx.item).await;
}
}
},
@ -391,7 +391,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
#(#get_params)*
#guard
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position()).await;
return OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await;
}
});
@ -482,7 +482,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #self_ty #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -58,7 +58,7 @@ pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<
async fn resolve(
&self,
_: &#crate_name::ContextSelectionSet<'_>,
_pos: #crate_name::Pos,
_field: &#crate_name::Positioned<#crate_name::parser::query::Field>
) -> #crate_name::Result<#crate_name::serde_json::Value> {
self.to_json()
}

View File

@ -117,7 +117,7 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
#guard
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))?;
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.position()).await;
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
}
});
}
@ -185,7 +185,7 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -242,7 +242,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
#(#get_params)*
#guard
let field_name = std::sync::Arc::new(ctx.result_name().to_string());
let field_selection_set = std::sync::Arc::new(ctx.selection_set.clone());
let field = std::sync::Arc::new(ctx.item.clone());
let pos = ctx.position();
let schema_env = schema_env.clone();
@ -252,7 +252,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
move |msg| {
let schema_env = schema_env.clone();
let query_env = query_env.clone();
let field_selection_set = field_selection_set.clone();
let field = field.clone();
let field_name = field_name.clone();
async move {
let resolve_id = std::sync::atomic::AtomicUsize::default();
@ -262,11 +262,11 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
parent: None,
segment: #crate_name::QueryPathSegment::Name(&field_name),
}),
&*field_selection_set,
&field.selection_set,
&resolve_id,
None,
);
#crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, pos).await
#crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, &*field).await
}
}
})

View File

@ -150,7 +150,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -1,9 +1,9 @@
use crate::parser::Pos;
use crate::registry::Registry;
use crate::{
registry, Context, ContextSelectionSet, FieldResult, InputValueResult, QueryError, Result,
Value, ID,
registry, Context, ContextSelectionSet, FieldResult, InputValueResult, Positioned, QueryError,
Result, Value, ID,
};
use async_graphql_parser::query::Field;
use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin;
@ -59,7 +59,11 @@ pub trait InputValueType: Type + Sized {
#[async_trait::async_trait]
pub trait OutputValueType: Type {
/// Resolve an output value to `serde_json::Value`.
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value>;
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value>;
}
#[allow(missing_docs)]
@ -166,8 +170,12 @@ impl<T: Type + Send + Sync> Type for &T {
#[async_trait::async_trait]
impl<T: OutputValueType + Send + Sync> OutputValueType for &T {
#[allow(clippy::trivially_copy_pass_by_ref)]
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
T::resolve(*self, ctx, pos).await
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
T::resolve(*self, ctx, field).await
}
}
@ -185,8 +193,12 @@ impl<T: Type + Send + Sync> Type for Box<T> {
impl<T: OutputValueType + Send + Sync> OutputValueType for Box<T> {
#[allow(clippy::trivially_copy_pass_by_ref)]
#[allow(clippy::borrowed_box)]
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
T::resolve(&*self, ctx, pos).await
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
T::resolve(&*self, ctx, field).await
}
}
@ -203,8 +215,12 @@ impl<T: Type + Send + Sync> Type for Arc<T> {
#[async_trait::async_trait]
impl<T: OutputValueType + Send + Sync> OutputValueType for Arc<T> {
#[allow(clippy::trivially_copy_pass_by_ref)]
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
T::resolve(&*self, ctx, pos).await
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
T::resolve(&*self, ctx, field).await
}
}
@ -227,15 +243,15 @@ impl<T: OutputValueType + Sync> OutputValueType for FieldResult<T> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
pos: Pos,
field: &Positioned<Field>,
) -> crate::Result<serde_json::Value> {
match self {
Ok(value) => Ok(OutputValueType::resolve(value, ctx, pos).await?),
Ok(value) => Ok(OutputValueType::resolve(value, ctx, field).await?),
Err(err) => Err(err.clone().into_error_with_path(
pos,
field.position(),
match &ctx.path_node {
Some(path) => path.to_json(),
None => serde_json::Value::Null,
None => Vec::new(),
},
)),
}

View File

@ -196,7 +196,7 @@ impl<'a> QueryPathNode<'a> {
}
#[doc(hidden)]
pub fn to_json(&self) -> serde_json::Value {
pub fn to_json(&self) -> Vec<serde_json::Value> {
let mut path: Vec<serde_json::Value> = Vec::new();
self.for_each(|segment| {
path.push(match segment {
@ -204,7 +204,7 @@ impl<'a> QueryPathNode<'a> {
QueryPathSegment::Name(name) => (*name).to_string().into(),
})
});
path.into()
path
}
}
@ -242,19 +242,17 @@ pub type BoxDeferFuture =
Pin<Box<dyn Future<Output = Result<(QueryResponse, DeferList)>> + Send + 'static>>;
#[doc(hidden)]
#[derive(Default)]
pub struct DeferList(pub Mutex<Vec<BoxDeferFuture>>);
pub struct DeferList {
pub path_prefix: Vec<serde_json::Value>,
pub futures: Mutex<Vec<BoxDeferFuture>>,
}
impl DeferList {
pub(crate) fn into_inner(self) -> Vec<BoxDeferFuture> {
self.0.into_inner()
}
pub(crate) fn append<F>(&self, fut: F)
where
F: Future<Output = Result<(QueryResponse, DeferList)>> + Send + 'static,
{
self.0.lock().push(Box::pin(fut));
self.futures.lock().push(Box::pin(fut));
}
}
@ -266,7 +264,8 @@ pub struct ContextBase<'a, T> {
pub(crate) resolve_id: ResolveId,
pub(crate) inc_resolve_id: &'a AtomicUsize,
pub(crate) extensions: &'a [BoxExtension],
pub(crate) item: T,
#[doc(hidden)]
pub item: T,
pub(crate) schema_env: &'a SchemaEnv,
pub(crate) query_env: &'a QueryEnv,
pub(crate) defer_list: Option<&'a DeferList>,

View File

@ -59,10 +59,14 @@ impl FieldError {
}
#[doc(hidden)]
pub fn into_error_with_path(self, pos: Pos, path: serde_json::Value) -> Error {
pub fn into_error_with_path(self, pos: Pos, path: Vec<serde_json::Value>) -> Error {
Error::Query {
pos,
path: Some(path),
path: if !path.is_empty() {
Some(path.into())
} else {
None
},
err: QueryError::FieldError {
err: self.0,
extended_error: self.1,

View File

@ -90,7 +90,7 @@ impl Extension for ApolloTracing {
inner.pending_resolves.insert(
info.resolve_id.current,
PendingResolve {
path: info.path_node.to_json(),
path: info.path_node.to_json().into(),
field_name: info.path_node.field_name().to_string(),
parent_type: info.parent_type.to_string(),
return_type: info.return_type.to_string(),

View File

@ -96,7 +96,6 @@ where
let mut file =
tempfile::tempfile().map_err(ParseRequestError::Io)?;
while let Some(chunk) = field.chunk().await.unwrap() {
println!("{:?}", chunk);
file.write(&chunk).map_err(ParseRequestError::Io)?;
}
file.seek(SeekFrom::Start(0))?;

View File

@ -59,6 +59,10 @@ impl Serialize for GQLResponse {
match &self.0 {
Ok(res) => {
let mut map = serializer.serialize_map(None)?;
if let Some(path) = &res.path {
map.serialize_key("path")?;
map.serialize_value(path)?;
}
map.serialize_key("data")?;
map.serialize_value(&res.data)?;
if res.extensions.is_some() {

View File

@ -75,6 +75,7 @@
#![warn(missing_docs)]
#![allow(clippy::needless_doctest_main)]
#![allow(clippy::needless_lifetimes)]
#![recursion_limit = "256"]
#[macro_use]
extern crate thiserror;

View File

@ -43,7 +43,7 @@ pub trait IntoQueryBuilder: Sized {
#[derive(Debug)]
pub struct QueryResponse {
/// Path for subsequent response
pub path: Option<serde_json::Value>,
pub path: Option<Vec<serde_json::Value>>,
/// Data of query result
pub data: serde_json::Value,
@ -56,34 +56,42 @@ pub struct QueryResponse {
}
impl QueryResponse {
pub(crate) fn merge(&mut self, resp: QueryResponse) {
if let Some(serde_json::Value::Array(items)) = resp.path {
let mut p = &mut self.data;
for item in items {
match item {
serde_json::Value::String(name) => {
if let serde_json::Value::Object(obj) = p {
if let Some(next) = obj.get_mut(&name) {
p = next;
continue;
}
}
return;
}
serde_json::Value::Number(idx) => {
if let serde_json::Value::Array(array) = p {
if let Some(next) = array.get_mut(idx.as_i64().unwrap() as usize) {
p = next;
continue;
}
}
return;
}
_ => {}
}
}
*p = resp.data;
pub(crate) fn apply_path_prefix(mut self, mut prefix: Vec<serde_json::Value>) -> Self {
if let Some(path) = &mut self.path {
prefix.extend(path.drain(..));
*path = prefix;
} else {
self.path = Some(prefix);
}
self
}
pub(crate) fn merge(&mut self, resp: QueryResponse) {
let mut p = &mut self.data;
for item in resp.path.unwrap_or_default() {
match item {
serde_json::Value::String(name) => {
if let serde_json::Value::Object(obj) = p {
if let Some(next) = obj.get_mut(&name) {
p = next;
continue;
}
}
return;
}
serde_json::Value::Number(idx) => {
if let serde_json::Value::Array(array) = p {
if let Some(next) = array.get_mut(idx.as_i64().unwrap() as usize) {
p = next;
continue;
}
}
return;
}
_ => {}
}
}
*p = resp.data;
}
}
@ -162,19 +170,26 @@ impl QueryBuilder {
let (first_resp, defer_list) = self.execute_first(&schema).await?;
yield first_resp;
let mut current_defer_list = defer_list.into_inner();
let mut current_defer_list = Vec::new();
for fut in defer_list.futures.into_inner() {
current_defer_list.push((defer_list.path_prefix.clone(), fut));
}
loop {
let mut new_defer_list = Vec::new();
for defer in current_defer_list {
let mut res = defer.await?;
new_defer_list.extend((res.1).into_inner());
yield res.0;
let mut next_defer_list = Vec::new();
for (path_prefix, defer) in current_defer_list {
let (res, mut defer_list) = defer.await?;
for fut in defer_list.futures.into_inner() {
let mut next_path_prefix = path_prefix.clone();
next_path_prefix.extend(defer_list.path_prefix.clone());
next_defer_list.push((next_path_prefix, fut));
}
yield res.apply_path_prefix(path_prefix);
}
if new_defer_list.is_empty() {
if next_defer_list.is_empty() {
break;
}
current_defer_list = new_defer_list;
current_defer_list = next_defer_list;
}
};
Box::pin(stream)
@ -250,7 +265,10 @@ impl QueryBuilder {
document,
Arc::new(self.ctx_data.unwrap_or_default()),
);
let defer_list = DeferList::default();
let defer_list = DeferList {
path_prefix: Vec::new(),
futures: Default::default(),
};
let ctx = ContextBase {
path_node: None,
resolve_id: ResolveId::root(),

View File

@ -1,9 +1,9 @@
use crate::parser::Pos;
use crate::{
registry, ContextSelectionSet, InputValueError, InputValueResult, OutputValueType, Result,
ScalarType, Type, Value,
registry, ContextSelectionSet, InputValueError, InputValueResult, OutputValueType, Positioned,
Result, ScalarType, Type, Value,
};
use async_graphql_derive::Scalar;
use async_graphql_parser::query::Field;
use std::borrow::Cow;
/// The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
@ -40,7 +40,11 @@ impl<'a> Type for &'a str {
#[async_trait::async_trait]
impl<'a> OutputValueType for &'a str {
async fn resolve(&self, _: &ContextSelectionSet<'_>, _pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
_: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> Result<serde_json::Value> {
Ok((*self).into())
}
}

View File

@ -283,6 +283,16 @@ where
QueryBuilder::new(query_source).execute(self).await
}
/// Execute the query without create the `QueryBuilder`, returns a stream, the first result being the query result,
/// followed by the incremental result. Only when there are `@defer` and `@stream` directives
/// in the query will there be subsequent incremental results.
pub async fn execute_stream(
&self,
query_source: &str,
) -> impl Stream<Item = Result<QueryResponse>> {
QueryBuilder::new(query_source).execute_stream(self).await
}
/// Create subscription stream, typically called inside the `SubscriptionTransport::handle_request` method
pub async fn create_subscription_stream(
&self,

View File

@ -3,8 +3,9 @@ use crate::types::connection::edge::Edge;
use crate::types::connection::page_info::PageInfo;
use crate::{
do_resolve, registry, Context, ContextSelectionSet, EmptyEdgeFields, Error, ObjectType,
OutputValueType, Pos, QueryError, Result, Type,
OutputValueType, Positioned, QueryError, Result, Type,
};
use async_graphql_parser::query::Field;
use indexmap::map::IndexMap;
use inflector::Inflector;
use itertools::Itertools;
@ -179,19 +180,17 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
if ctx.name.node == "pageInfo" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return OutputValueType::resolve(self.page_info().await, &ctx_obj, ctx.position())
.await;
return OutputValueType::resolve(self.page_info().await, &ctx_obj, ctx.item).await;
} else if ctx.name.node == "edges" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return OutputValueType::resolve(&self.edges().await, &ctx_obj, ctx.position()).await;
return OutputValueType::resolve(&self.edges().await, &ctx_obj, ctx.item).await;
} else if ctx.name.node == "totalCount" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return OutputValueType::resolve(&self.total_count().await, &ctx_obj, ctx.position())
.await;
return OutputValueType::resolve(&self.total_count().await, &ctx_obj, ctx.item).await;
} else if ctx.name.node == T::type_name().to_plural().to_camel_case() {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let items = self.nodes.iter().map(|(_, _, item)| item).collect_vec();
return OutputValueType::resolve(&items, &ctx_obj, ctx.position()).await;
return OutputValueType::resolve(&items, &ctx_obj, ctx.item).await;
}
Err(Error::Query {
@ -209,7 +208,11 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> OutputValueType
for Connection<T, E>
{
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> Result<serde_json::Value> {
do_resolve(ctx, self).await
}
}

View File

@ -1,7 +1,8 @@
use crate::{
do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Pos, Result,
Type,
do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned,
Result, Type,
};
use async_graphql_parser::query::Field;
use indexmap::map::IndexMap;
use std::borrow::Cow;
@ -105,7 +106,7 @@ where
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
if ctx.name.node == "node" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return OutputValueType::resolve(self.node().await, &ctx_obj, ctx.position()).await;
return OutputValueType::resolve(self.node().await, &ctx_obj, ctx.item).await;
} else if ctx.name.node == "cursor" {
return Ok(self.cursor().await.into());
}
@ -120,7 +121,11 @@ where
T: OutputValueType + Send + Sync + 'a,
E: ObjectType + Sync + Send + 'a,
{
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> Result<serde_json::Value> {
do_resolve(ctx, self).await
}
}

View File

@ -1,9 +1,14 @@
use crate::context::DeferList;
use crate::registry::Registry;
use crate::{ContextSelectionSet, OutputValueType, Pos, QueryResponse, Result, Type};
use crate::{ContextSelectionSet, OutputValueType, Positioned, QueryResponse, Result, Type};
use async_graphql_parser::query::Field;
use itertools::Itertools;
use std::borrow::Cow;
use std::sync::atomic::AtomicUsize;
/// Deferred type
///
/// Allows to defer the type of results returned, Only takes effect when the @defer directive exists on the field.
pub struct Deferred<T: Type + Send + Sync + Clone + 'static>(T);
impl<T: Type + Send + Sync + Clone + 'static> From<T> for Deferred<T> {
@ -24,38 +29,62 @@ impl<T: Type + Send + Sync + Clone + 'static> Type for Deferred<T> {
#[async_trait::async_trait]
impl<T: OutputValueType + Send + Sync + Clone + 'static> OutputValueType for Deferred<T> {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
if let Some(defer_list) = ctx.defer_list {
let obj = self.0.clone();
let schema_env = ctx.schema_env.clone();
let query_env = ctx.query_env.clone();
let field_selection_set = ctx.item.clone();
let path = ctx.path_node.as_ref().map(|path| path.to_json());
defer_list.append(async move {
let inc_resolve_id = AtomicUsize::default();
let defer_list = DeferList::default();
let ctx = query_env.create_context(
&schema_env,
None,
&field_selection_set,
&inc_resolve_id,
Some(&defer_list),
);
let data = obj.resolve(&ctx, pos).await?;
if ctx.is_defer(&field.directives) {
let obj = self.0.clone();
let schema_env = ctx.schema_env.clone();
let query_env = ctx.query_env.clone();
let mut field = field.clone();
Ok((
QueryResponse {
path,
data,
extensions: None,
cache_control: Default::default(),
},
defer_list,
))
});
Ok(serde_json::Value::Null)
} else {
OutputValueType::resolve(&self.0, ctx, pos).await
// remove @defer directive
if let Some((idx, _)) = field
.node
.directives
.iter()
.find_position(|d| d.name.as_str() == "defer")
{
field.node.directives.remove(idx);
}
let path_prefix = ctx
.path_node
.as_ref()
.map(|path| path.to_json())
.unwrap_or_default();
defer_list.append(async move {
let inc_resolve_id = AtomicUsize::default();
let defer_list = DeferList {
path_prefix: path_prefix.clone(),
futures: Default::default(),
};
let ctx = query_env.create_context(
&schema_env,
None,
&field.selection_set,
&inc_resolve_id,
Some(&defer_list),
);
let data = obj.resolve(&ctx, &field).await?;
Ok((
QueryResponse {
path: Some(path_prefix),
data,
extensions: None,
cache_control: Default::default(),
},
defer_list,
))
});
return Ok(serde_json::Value::Null);
}
}
OutputValueType::resolve(&self.0, ctx, field).await
}
}

View File

@ -1,7 +1,8 @@
use crate::{
registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos, QueryError,
Result, Type,
registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Positioned,
QueryError, Result, Type,
};
use async_graphql_parser::query::Field;
use std::borrow::Cow;
/// Empty mutation
@ -54,9 +55,13 @@ impl ObjectType for EmptyMutation {
#[async_trait::async_trait]
impl OutputValueType for EmptyMutation {
async fn resolve(&self, _ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
_ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
Err(Error::Query {
pos,
pos: field.position(),
path: None,
err: QueryError::NotConfiguredMutations,
})

View File

@ -1,8 +1,5 @@
use crate::context::QueryEnv;
use crate::{
registry, Context, ContextSelectionSet, Error, OutputValueType, Pos, QueryError, Result,
SchemaEnv, SubscriptionType, Type,
};
use crate::{registry, Context, Error, Pos, QueryError, Result, SchemaEnv, SubscriptionType, Type};
use futures::Stream;
use std::borrow::Cow;
use std::pin::Pin;
@ -52,14 +49,3 @@ impl SubscriptionType for EmptySubscription {
})
}
}
#[async_trait::async_trait]
impl OutputValueType for EmptySubscription {
async fn resolve(&self, _ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
Err(Error::Query {
pos,
path: None,
err: QueryError::NotConfiguredSubscriptions,
})
}
}

View File

@ -1,7 +1,8 @@
use crate::{
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Pos, Result,
Type, Value,
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
Result, Type, Value,
};
use async_graphql_parser::query::Field;
use std::borrow::Cow;
impl<T: Type> Type for Vec<T> {
@ -37,11 +38,15 @@ impl<T: InputValueType> InputValueType for Vec<T> {
#[allow(clippy::ptr_arg)]
#[async_trait::async_trait]
impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
let mut futures = Vec::with_capacity(self.len());
for (idx, item) in self.iter().enumerate() {
let ctx_idx = ctx.with_index(idx);
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, pos).await });
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
}
Ok(futures::future::try_join_all(futures).await?.into())
}
@ -59,11 +64,15 @@ impl<T: Type> Type for &[T] {
#[async_trait::async_trait]
impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
let mut futures = Vec::with_capacity(self.len());
for (idx, item) in (*self).iter().enumerate() {
let ctx_idx = ctx.with_index(idx);
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, pos).await });
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
}
Ok(futures::future::try_join_all(futures).await?.into())
}

View File

@ -1,7 +1,8 @@
use crate::{
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Pos, Result,
Type, Value,
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
Result, Type, Value,
};
use async_graphql_parser::query::Field;
use std::borrow::Cow;
impl<T: Type> Type for Option<T> {
@ -30,10 +31,13 @@ impl<T: InputValueType> InputValueType for Option<T> {
#[async_trait::async_trait]
impl<T: OutputValueType + Sync> OutputValueType for Option<T> {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, pos: Pos) -> Result<serde_json::Value> where
{
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> Result<serde_json::Value> where {
if let Some(inner) = self {
OutputValueType::resolve(inner, ctx, pos).await
OutputValueType::resolve(inner, ctx, field).await
} else {
Ok(serde_json::Value::Null)
}

View File

@ -1,10 +1,11 @@
use crate::model::{__Schema, __Type};
use crate::scalars::Any;
use crate::{
do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos,
QueryError, Result, Type, Value,
do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType,
Positioned, QueryError, Result, Type, Value,
};
use async_graphql_derive::SimpleObject;
use async_graphql_parser::query::Field;
use indexmap::map::IndexMap;
use std::borrow::Cow;
@ -84,7 +85,7 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
if self.disable_introspection {
return Err(Error::Query {
pos: ctx.position(),
path: Some(ctx.path_node.as_ref().unwrap().to_json()),
path: Some(ctx.path_node.as_ref().unwrap().to_json().into()),
err: QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
object: Self::type_name().to_string(),
@ -98,7 +99,7 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
registry: &ctx.schema_env.registry,
},
&ctx_obj,
ctx.position(),
ctx.item,
)
.await;
} else if ctx.name.node == "__type" {
@ -111,7 +112,7 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
.get(&type_name)
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
&ctx_obj,
ctx.position(),
ctx.item,
)
.await;
} else if ctx.name.node == "_entities" {
@ -128,7 +129,7 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
sdl: Some(ctx.schema_env.registry.create_federation_sdl()),
},
&ctx_obj,
ctx.position(),
ctx.item,
)
.await;
}
@ -139,7 +140,11 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
#[async_trait::async_trait]
impl<T: ObjectType + Send + Sync> OutputValueType for QueryRoot<T> {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _pos: Pos) -> Result<serde_json::Value> {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> Result<serde_json::Value> {
do_resolve(ctx, self).await
}
}

View File

@ -1,21 +1,38 @@
use async_graphql::*;
use futures::StreamExt;
#[async_std::test]
pub async fn test_defer() {
struct Query {
value: i32,
#[derive(Clone)]
struct MyObj;
#[Object]
impl MyObj {
async fn value(&self) -> i32 {
20
}
async fn obj(&self) -> Deferred<MyObj> {
MyObj.into()
}
}
struct Query;
#[Object]
impl Query {
async fn value(&self) -> Deferred<i32> {
10.into()
}
async fn obj(&self) -> Deferred<MyObj> {
MyObj.into()
}
}
let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription);
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = r#"{
value
value @defer
}"#;
assert_eq!(
schema.execute(&query).await.unwrap().data,
@ -23,4 +40,55 @@ pub async fn test_defer() {
"value": 10,
})
);
let query = r#"{
value @defer
obj @defer {
value
obj @defer {
value
}
}
}"#;
assert_eq!(
schema.execute(&query).await.unwrap().data,
serde_json::json!({
"value": 10,
"obj": {
"value": 20,
"obj": {
"value": 20
}
}
})
);
let mut stream = schema.execute_stream(&query).await;
assert_eq!(
stream.next().await.unwrap().unwrap().data,
serde_json::json!({
"value": null,
"obj": null,
})
);
let next_resp = stream.next().await.unwrap().unwrap();
assert_eq!(next_resp.path, Some(vec![serde_json::json!("value")]));
assert_eq!(next_resp.data, serde_json::json!(10));
let next_resp = stream.next().await.unwrap().unwrap();
assert_eq!(next_resp.path, Some(vec![serde_json::json!("obj")]));
assert_eq!(
next_resp.data,
serde_json::json!({"value": 20, "obj": null})
);
let next_resp = stream.next().await.unwrap().unwrap();
assert_eq!(
next_resp.path,
Some(vec![serde_json::json!("obj"), serde_json::json!("obj")])
);
assert_eq!(next_resp.data, serde_json::json!({"value": 20}));
assert!(stream.next().await.is_none());
}