Add mutation resolver
This commit is contained in:
parent
e6eb331ae4
commit
197eed8629
|
@ -1,5 +1,5 @@
|
||||||
use crate::registry::Registry;
|
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::query::{Field, Value};
|
||||||
use graphql_parser::Pos;
|
use graphql_parser::Pos;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
|
@ -76,6 +76,7 @@ mod context;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod extensions;
|
pub mod extensions;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod mutation_resolver;
|
||||||
mod query;
|
mod query;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
mod scalars;
|
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::context::Data;
|
||||||
use crate::extensions::BoxExtension;
|
use crate::extensions::BoxExtension;
|
||||||
|
use crate::mutation_resolver::do_mutation_resolve;
|
||||||
use crate::registry::CacheControl;
|
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 crate::{ObjectType, QueryError, Variables};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use graphql_parser::query::{
|
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());
|
self.extensions.iter().for_each(|e| e.execution_start());
|
||||||
let data = if is_query {
|
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 {
|
} 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());
|
self.extensions.iter().for_each(|e| e.execution_end());
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,7 @@ impl Type {
|
||||||
match self {
|
match self {
|
||||||
Type::Interface { possible_types, .. } => possible_types.contains(type_name),
|
Type::Interface { possible_types, .. } => possible_types.contains(type_name),
|
||||||
Type::Union { possible_types, .. } => possible_types.contains(type_name),
|
Type::Union { possible_types, .. } => possible_types.contains(type_name),
|
||||||
|
Type::Object { name, .. } => name == type_name,
|
||||||
_ => false,
|
_ => 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