Add defer tests
This commit is contained in:
parent
75bfba057a
commit
6f924efcf4
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
44
src/base.rs
44
src/base.rs
|
@ -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(),
|
||||
},
|
||||
)),
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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))?;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#![warn(missing_docs)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
|
92
src/query.rs
92
src/query.rs
|
@ -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(),
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user