Add mutation resolver
This commit is contained in:
parent
e6eb331ae4
commit
197eed8629
|
@ -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;
|
||||
|
|
|
@ -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
151
src/mutation_resolver.rs
Normal 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(())
|
||||
})
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
44
tests/mutation.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user