Add mutation resolver

This commit is contained in:
sunli 2020-04-03 22:19:15 +08:00
parent e6eb331ae4
commit 197eed8629
6 changed files with 202 additions and 4 deletions

View File

@ -1,5 +1,5 @@
use crate::registry::Registry;
use crate::{registry, Context, ContextSelectionSet, Error, QueryError, Result, ID};
use crate::{registry, Context, ContextSelectionSet, Result, ID};
use graphql_parser::query::{Field, Value};
use graphql_parser::Pos;
use std::borrow::Cow;

View File

@ -76,6 +76,7 @@ mod context;
mod error;
pub mod extensions;
mod model;
mod mutation_resolver;
mod query;
mod resolver;
mod scalars;

151
src/mutation_resolver.rs Normal file
View File

@ -0,0 +1,151 @@
use crate::extensions::ResolveInfo;
use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result};
use graphql_parser::query::{Selection, TypeCondition};
use std::future::Future;
use std::pin::Pin;
type BoxMutationFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
#[allow(missing_docs)]
pub async fn do_mutation_resolve<'a, T: ObjectType + Send + Sync>(
ctx: &'a ContextSelectionSet<'a>,
root: &'a T,
) -> Result<serde_json::Value> {
let mut values = serde_json::Map::new();
do_resolve(ctx, root, &mut values).await?;
Ok(values.into())
}
fn do_resolve<'a, T: ObjectType + Send + Sync>(
ctx: &'a ContextSelectionSet<'a>,
root: &'a T,
values: &'a mut serde_json::Map<String, serde_json::Value>,
) -> BoxMutationFuture<'a> {
Box::pin(async move {
if ctx.items.is_empty() {
return Err(Error::Query {
pos: ctx.span.0,
path: None,
err: QueryError::MustHaveSubFields {
object: T::type_name().to_string(),
},
});
}
for selection in &ctx.item.items {
match selection {
Selection::Field(field) => {
if ctx.is_skip(&field.directives)? {
continue;
}
if field.name.as_str() == "__typename" {
values.insert(
"__typename".to_string(),
root.introspection_type_name().to_string().into(),
);
continue;
}
let ctx_field = ctx.with_field(field);
let field_name = ctx_field.result_name().to_string();
let resolve_id = ctx_field.get_resolve_id();
if !ctx_field.extensions.is_empty() {
let resolve_info = ResolveInfo {
resolve_id,
path_node: ctx_field.path_node.as_ref().unwrap(),
parent_type: &T::type_name(),
return_type: match ctx_field
.registry
.types
.get(T::type_name().as_ref())
.and_then(|ty| ty.field_by_name(field.name.as_str()))
.map(|field| &field.ty)
{
Some(ty) => &ty,
None => {
return Err(Error::Query {
pos: field.position,
path: None,
err: QueryError::FieldNotFound {
field_name: field.name.clone(),
object: T::type_name().to_string(),
},
});
}
},
};
ctx_field
.extensions
.iter()
.for_each(|e| e.resolve_field_start(&resolve_info));
}
let value = root.resolve_field(&ctx_field, field).await?;
values.insert(field_name, value);
if !ctx_field.extensions.is_empty() {
ctx_field
.extensions
.iter()
.for_each(|e| e.resolve_field_end(resolve_id));
}
}
Selection::FragmentSpread(fragment_spread) => {
if ctx.is_skip(&fragment_spread.directives)? {
continue;
}
if let Some(fragment) =
ctx.fragments.get(fragment_spread.fragment_name.as_str())
{
do_resolve(
&ctx.with_selection_set(&fragment.selection_set),
root,
values,
)
.await?;
} else {
return Err(Error::Query {
pos: fragment_spread.position,
path: None,
err: QueryError::UnknownFragment {
name: fragment_spread.fragment_name.clone(),
},
});
}
}
Selection::InlineFragment(inline_fragment) => {
if ctx.is_skip(&inline_fragment.directives)? {
continue;
}
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
let mut futures = Vec::new();
root.collect_inline_fields(
name,
inline_fragment.position,
&ctx.with_selection_set(&inline_fragment.selection_set),
&mut futures,
)?;
for fut in futures {
let (name, value) = fut.await?;
values.insert(name, value);
}
} else {
do_resolve(
&ctx.with_selection_set(&inline_fragment.selection_set),
root,
values,
)
.await?;
}
}
}
}
Ok(())
})
}

View File

@ -1,7 +1,8 @@
use crate::context::Data;
use crate::extensions::BoxExtension;
use crate::mutation_resolver::do_mutation_resolve;
use crate::registry::CacheControl;
use crate::{ContextBase, Error, OutputValueType, Result, Schema};
use crate::{do_resolve, ContextBase, Error, Result, Schema};
use crate::{ObjectType, QueryError, Variables};
use bytes::Bytes;
use graphql_parser::query::{
@ -162,9 +163,9 @@ impl<Query, Mutation, Subscription> QueryBuilder<Query, Mutation, Subscription>
self.extensions.iter().for_each(|e| e.execution_start());
let data = if is_query {
OutputValueType::resolve(&self.schema.0.query, &ctx, selection_set.span.0).await?
do_resolve(&ctx, &self.schema.0.query).await?
} else {
OutputValueType::resolve(&self.schema.0.mutation, &ctx, selection_set.span.0).await?
do_mutation_resolve(&ctx, &self.schema.0.mutation).await?
};
self.extensions.iter().for_each(|e| e.execution_end());

View File

@ -245,6 +245,7 @@ impl Type {
match self {
Type::Interface { possible_types, .. } => possible_types.contains(type_name),
Type::Union { possible_types, .. } => possible_types.contains(type_name),
Type::Object { name, .. } => name == type_name,
_ => false,
}
}

44
tests/mutation.rs Normal file
View File

@ -0,0 +1,44 @@
use async_graphql::*;
use futures::lock::Mutex;
use std::sync::Arc;
use std::time::Duration;
#[async_std::test]
pub async fn test_list_type() {
struct QueryRoot;
type List = Arc<Mutex<Vec<i32>>>;
#[Object]
impl QueryRoot {}
struct MutationRoot;
#[Object]
impl MutationRoot {
#[field]
async fn append1(&self, ctx: &Context<'_>) -> bool {
async_std::task::sleep(Duration::from_secs(1)).await;
ctx.data::<List>().lock().await.push(1);
true
}
#[field]
async fn append2(&self, ctx: &Context<'_>) -> bool {
async_std::task::sleep(Duration::from_millis(500)).await;
ctx.data::<List>().lock().await.push(2);
true
}
}
let list = List::default();
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
.data(list.clone())
.finish();
schema
.execute("mutation { append1 append2 }")
.await
.unwrap();
assert_eq!(list.lock().await[0], 1);
assert_eq!(list.lock().await[1], 2);
}