Rework error propagation. #531
This commit is contained in:
parent
b15e421f1e
commit
553bf37abe
|
@ -334,7 +334,7 @@ pub fn generate(
|
|||
#guard
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
let res = #resolve_obj;
|
||||
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||
return #crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
|||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::OutputType for #ident {
|
||||
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
#crate_name::resolver_utils::enum_value(*self)
|
||||
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
::std::result::Result::Ok(#crate_name::resolver_utils::enum_value(*self))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -296,7 +296,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
if ctx.item.node.name.node == #name {
|
||||
#(#get_params)*
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await));
|
||||
return #crate_name::OutputType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -373,7 +373,11 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>,
|
||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,8 +103,8 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
|
|||
&self,
|
||||
_: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||
) -> #crate_name::Value {
|
||||
#crate_name::ScalarType::to_value(self)
|
||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
Ok(#crate_name::ScalarType::to_value(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -212,7 +212,7 @@ pub fn generate(
|
|||
let do_find = quote! {
|
||||
self.#field_ident(ctx, #(#use_keys),*)
|
||||
.await.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err)
|
||||
.into_server_error(ctx.item.pos))?
|
||||
.into_server_error(ctx.item.pos))
|
||||
};
|
||||
|
||||
find_entities.push((
|
||||
|
@ -221,9 +221,13 @@ pub fn generate(
|
|||
#(#cfg_attrs)*
|
||||
if typename == &<#entity_type as #crate_name::Type>::type_name() {
|
||||
if let (#(#key_pat),*) = (#(#key_getter),*) {
|
||||
#(#requires_getter)*
|
||||
let f = async move {
|
||||
#(#requires_getter)*
|
||||
#do_find
|
||||
};
|
||||
let obj = f.await.map_err(|err| ctx.set_error_path(err))?;
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&#do_find, &ctx_obj, ctx.item).await));
|
||||
return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -517,7 +521,7 @@ pub fn generate(
|
|||
let resolve_obj = quote! {
|
||||
{
|
||||
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
||||
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))?
|
||||
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -535,11 +539,14 @@ pub fn generate(
|
|||
resolvers.push(quote! {
|
||||
#(#cfg_attrs)*
|
||||
if ctx.item.node.name.node == #field_name {
|
||||
#(#get_params)*
|
||||
#guard
|
||||
let f = async move {
|
||||
#(#get_params)*
|
||||
#guard
|
||||
#resolve_obj
|
||||
};
|
||||
let obj = f.await.map_err(|err| ctx.set_error_path(err))?;
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
let res = #resolve_obj;
|
||||
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||
return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -633,7 +640,11 @@ pub fn generate(
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::OutputType for #shadow_type<#generics_params> #where_clause {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ pub fn generate(
|
|||
&self,
|
||||
_: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||
) -> #crate_name::Value {
|
||||
#crate_name::ScalarType::to_value(self)
|
||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
::std::result::Result::Ok(#crate_name::ScalarType::to_value(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -124,10 +124,13 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
|
||||
resolvers.push(quote! {
|
||||
if ctx.item.node.name.node == #field_name {
|
||||
#guard
|
||||
let res = self.#ident(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?;
|
||||
let f = async move {
|
||||
#guard
|
||||
self.#ident(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))
|
||||
};
|
||||
let obj = f.await.map_err(|err| ctx.set_error_path(err))?;
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||
return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -212,7 +215,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +280,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::OutputType for #concrete_type {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,7 +367,9 @@ pub fn generate(
|
|||
return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::Type>::qualified_type_name(),
|
||||
};
|
||||
let resolve_fut = async {
|
||||
::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field).await))
|
||||
#crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field)
|
||||
.await
|
||||
.map(::std::option::Option::Some)
|
||||
};
|
||||
#crate_name::futures_util::pin_mut!(resolve_fut);
|
||||
let mut resp = query_env.extensions.resolve(ri, &mut resolve_fut).await.map(|value| {
|
||||
|
@ -375,12 +377,10 @@ pub fn generate(
|
|||
map.insert(::std::clone::Clone::clone(&field_name), value.unwrap_or_default());
|
||||
#crate_name::Response::new(#crate_name::Value::Object(map))
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
#crate_name::Response::from_errors(::std::vec![
|
||||
err.with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))])
|
||||
])
|
||||
});
|
||||
resp.errors = ::std::mem::take(&mut query_env.errors.lock().unwrap());
|
||||
.unwrap_or_else(|err| #crate_name::Response::from_errors(::std::vec![err]));
|
||||
|
||||
use ::std::iter::Extend;
|
||||
resp.errors.extend(::std::mem::take(&mut *query_env.errors.lock().unwrap()));
|
||||
resp
|
||||
};
|
||||
#crate_name::futures_util::pin_mut!(execute_fut);
|
||||
|
|
|
@ -200,7 +200,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
33
src/base.rs
33
src/base.rs
|
@ -57,7 +57,11 @@ pub trait InputType: Type + Send + Sync + Sized {
|
|||
#[async_trait::async_trait]
|
||||
pub trait OutputType: Type + Send + Sync {
|
||||
/// Resolve an output value to `async_graphql::Value`.
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value;
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value>;
|
||||
}
|
||||
|
||||
impl<T: Type + ?Sized> Type for &T {
|
||||
|
@ -73,7 +77,11 @@ impl<T: Type + ?Sized> Type for &T {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + ?Sized> OutputType for &T {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
T::resolve(*self, ctx, field).await
|
||||
}
|
||||
}
|
||||
|
@ -94,12 +102,15 @@ impl<T: Type, E: Into<Error> + Send + Sync + Clone> Type for Result<T, E> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for Result<T, E> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
match self {
|
||||
Ok(value) => value.resolve(ctx, field).await,
|
||||
Err(err) => {
|
||||
ctx.add_error(err.clone().into().into_server_error(field.pos));
|
||||
Value::Null
|
||||
return Err(ctx.set_error_path(err.clone().into().into_server_error(field.pos)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +144,11 @@ impl<T: Type + ?Sized> Type for Box<T> {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
T::resolve(&**self, ctx, field).await
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +179,11 @@ impl<T: Type + ?Sized> Type for Arc<T> {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + ?Sized> OutputType for Arc<T> {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
T::resolve(&**self, ctx, field).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,33 +281,27 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_error_path(&self, error: ServerError) -> ServerError {
|
||||
if let Some(node) = self.path_node {
|
||||
let mut path = Vec::new();
|
||||
node.for_each(|current_node| {
|
||||
path.push(match current_node {
|
||||
QueryPathSegment::Name(name) => PathSegment::Field((*name).to_string()),
|
||||
QueryPathSegment::Index(idx) => PathSegment::Index(*idx),
|
||||
})
|
||||
});
|
||||
ServerError { path, ..error }
|
||||
} else {
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
/// Report a resolver error.
|
||||
///
|
||||
/// When implementing `OutputType`, if an error occurs, call this function to report this error and return `Value::Null`.
|
||||
pub fn add_error(&self, error: ServerError) {
|
||||
match self.path_node {
|
||||
Some(node) => {
|
||||
let mut path = Vec::new();
|
||||
node.for_each(|current_node| {
|
||||
path.push(match current_node {
|
||||
QueryPathSegment::Name(name) => PathSegment::Field((*name).to_string()),
|
||||
QueryPathSegment::Index(idx) => PathSegment::Index(*idx),
|
||||
})
|
||||
});
|
||||
self.query_env
|
||||
.errors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(ServerError { path, ..error });
|
||||
}
|
||||
None => {
|
||||
self.query_env
|
||||
.errors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(ServerError { ..error });
|
||||
}
|
||||
}
|
||||
self.query_env.errors.lock().unwrap().push(error);
|
||||
}
|
||||
|
||||
/// Gets the global data defined in the `Context` or `Schema`.
|
||||
|
|
|
@ -62,7 +62,7 @@ impl<T: ContainerType> ContainerType for &T {
|
|||
pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
) -> Value {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container_inner(ctx, root, true).await
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
|
|||
pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
) -> Value {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container_inner(ctx, root, false).await
|
||||
}
|
||||
|
||||
|
@ -104,20 +104,16 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>(
|
|||
ctx: &ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
parallel: bool,
|
||||
) -> Value {
|
||||
) -> ServerResult<Value> {
|
||||
let mut fields = Fields(Vec::new());
|
||||
|
||||
if let Err(err) = fields.add_set(ctx, root) {
|
||||
ctx.add_error(err);
|
||||
return Value::Null;
|
||||
}
|
||||
fields.add_set(ctx, root)?;
|
||||
|
||||
let res = if parallel {
|
||||
futures_util::future::join_all(fields.0).await
|
||||
futures_util::future::try_join_all(fields.0).await?
|
||||
} else {
|
||||
let mut results = Vec::with_capacity(fields.0.len());
|
||||
for field in fields.0 {
|
||||
results.push(field.await);
|
||||
results.push(field.await?);
|
||||
}
|
||||
results
|
||||
};
|
||||
|
@ -126,10 +122,10 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>(
|
|||
for (name, value) in res {
|
||||
insert_value(&mut map, name, value);
|
||||
}
|
||||
Value::Object(map)
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
|
||||
type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = (Name, Value)> + 'a + Send>>;
|
||||
type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;
|
||||
|
||||
/// A set of fields on an container that are being selected.
|
||||
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
||||
|
@ -154,9 +150,9 @@ impl<'a> Fields<'a> {
|
|||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let typename = root.introspection_type_name().into_owned();
|
||||
|
||||
self.0.push(Box::pin(
|
||||
async move { (field_name, Value::String(typename)) },
|
||||
));
|
||||
self.0.push(Box::pin(async move {
|
||||
Ok((field_name, Value::String(typename)))
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -178,13 +174,10 @@ impl<'a> Fields<'a> {
|
|||
let extensions = &ctx.query_env.extensions;
|
||||
|
||||
if extensions.is_empty() {
|
||||
match root.resolve_field(&ctx_field).await {
|
||||
Ok(value) => (field_name, value.unwrap_or_default()),
|
||||
Err(e) => {
|
||||
ctx_field.add_error(e);
|
||||
(field_name, Value::Null)
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
field_name,
|
||||
root.resolve_field(&ctx_field).await?.unwrap_or_default(),
|
||||
))
|
||||
} else {
|
||||
let type_name = T::type_name();
|
||||
let resolve_info = ResolveInfo {
|
||||
|
@ -202,28 +195,26 @@ impl<'a> Fields<'a> {
|
|||
{
|
||||
Some(ty) => &ty,
|
||||
None => {
|
||||
ctx_field.add_error(ServerError::new(
|
||||
return Err(ServerError::new(
|
||||
format!(
|
||||
r#"Cannot query field "{}" on type "{}"."#,
|
||||
field_name, type_name
|
||||
),
|
||||
Some(ctx_field.item.pos),
|
||||
));
|
||||
return (field_name, Value::Null);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let resolve_fut = root.resolve_field(&ctx_field);
|
||||
futures_util::pin_mut!(resolve_fut);
|
||||
let res = extensions.resolve(resolve_info, &mut resolve_fut).await;
|
||||
match res {
|
||||
Ok(value) => (field_name, value.unwrap_or_default()),
|
||||
Err(err) => {
|
||||
ctx.add_error(err);
|
||||
(field_name, Value::Null)
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
field_name,
|
||||
extensions
|
||||
.resolve(resolve_info, &mut resolve_fut)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::extensions::ResolveInfo;
|
||||
use crate::parser::types::Field;
|
||||
use crate::{ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||
use crate::{ContextSelectionSet, OutputType, Positioned, ServerResult, Type, Value};
|
||||
|
||||
/// Resolve an list by executing each of the items concurrently.
|
||||
pub async fn resolve_list<'a, T: OutputType + 'a>(
|
||||
|
@ -8,7 +8,7 @@ pub async fn resolve_list<'a, T: OutputType + 'a>(
|
|||
field: &Positioned<Field>,
|
||||
iter: impl IntoIterator<Item = T>,
|
||||
len: Option<usize>,
|
||||
) -> Value {
|
||||
) -> ServerResult<Value> {
|
||||
let extensions = &ctx.query_env.extensions;
|
||||
if !extensions.is_empty() {
|
||||
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
||||
|
@ -24,24 +24,35 @@ pub async fn resolve_list<'a, T: OutputType + 'a>(
|
|||
parent_type: &Vec::<T>::type_name(),
|
||||
return_type: &T::qualified_type_name(),
|
||||
};
|
||||
let resolve_fut =
|
||||
async { Ok(Some(OutputType::resolve(&item, &ctx_idx, field).await)) };
|
||||
let resolve_fut = async {
|
||||
OutputType::resolve(&item, &ctx_idx, field)
|
||||
.await
|
||||
.map(Option::Some)
|
||||
.map_err(|err| ctx_idx.set_error_path(err))
|
||||
};
|
||||
futures_util::pin_mut!(resolve_fut);
|
||||
extensions
|
||||
.resolve(resolve_info, &mut resolve_fut)
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("You definitely encountered a bug!")
|
||||
.map(|value| value.expect("You definitely encountered a bug!"))
|
||||
}
|
||||
});
|
||||
}
|
||||
Value::List(futures_util::future::join_all(futures).await)
|
||||
Ok(Value::List(
|
||||
futures_util::future::try_join_all(futures).await?,
|
||||
))
|
||||
} else {
|
||||
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
||||
for (idx, item) in iter.into_iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputType::resolve(&item, &ctx_idx, field).await });
|
||||
futures.push(async move {
|
||||
OutputType::resolve(&item, &ctx_idx, field)
|
||||
.await
|
||||
.map_err(|err| ctx_idx.set_error_path(err))
|
||||
});
|
||||
}
|
||||
Value::List(futures_util::future::join_all(futures).await)
|
||||
Ok(Value::List(
|
||||
futures_util::future::try_join_all(futures).await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,8 +154,8 @@ macro_rules! scalar_internal {
|
|||
&self,
|
||||
_: &$crate::ContextSelectionSet<'_>,
|
||||
_field: &$crate::Positioned<$crate::parser::types::Field>,
|
||||
) -> $crate::Value {
|
||||
$crate::ScalarType::to_value(self)
|
||||
) -> $crate::ServerResult<$crate::Value> {
|
||||
::std::result::Result::Ok($crate::ScalarType::to_value(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -468,20 +468,23 @@ where
|
|||
query_env: &env,
|
||||
};
|
||||
|
||||
let value = match &env.operation.node.ty {
|
||||
let res = match &env.operation.node.ty {
|
||||
OperationType::Query => resolve_container(&ctx, &self.query).await,
|
||||
OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await,
|
||||
OperationType::Subscription => {
|
||||
return Response::from_errors(vec![ServerError::new(
|
||||
"Subscriptions are not supported on this transport.",
|
||||
None,
|
||||
)]);
|
||||
}
|
||||
OperationType::Subscription => Err(ServerError::new(
|
||||
"Subscriptions are not supported on this transport.",
|
||||
None,
|
||||
)),
|
||||
};
|
||||
|
||||
let mut resp = Response::new(value)
|
||||
.http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap()));
|
||||
resp.errors = std::mem::take(&mut env.errors.lock().unwrap());
|
||||
let mut resp = match res {
|
||||
Ok(value) => Response::new(value)
|
||||
.http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap())),
|
||||
Err(err) => Response::from_errors(vec![err]),
|
||||
};
|
||||
|
||||
resp.errors
|
||||
.extend(std::mem::take(&mut *env.errors.lock().unwrap()));
|
||||
resp
|
||||
}
|
||||
|
||||
|
|
|
@ -212,14 +212,14 @@ where
|
|||
end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()),
|
||||
};
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(&page_info, &ctx_obj, ctx.item).await,
|
||||
));
|
||||
return OutputType::resolve(&page_info, &ctx_obj, ctx.item)
|
||||
.await
|
||||
.map(Some);
|
||||
} else if ctx.item.node.name.node == "edges" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(&self.edges, &ctx_obj, ctx.item).await,
|
||||
));
|
||||
return OutputType::resolve(&self.edges, &ctx_obj, ctx.item)
|
||||
.await
|
||||
.map(Some);
|
||||
}
|
||||
|
||||
self.additional_fields.resolve_field(ctx).await
|
||||
|
@ -234,7 +234,11 @@ where
|
|||
EC: ObjectType,
|
||||
EE: ObjectType,
|
||||
{
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,9 +122,9 @@ where
|
|||
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
||||
if ctx.item.node.name.node == "node" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(&self.node, &ctx_obj, ctx.item).await,
|
||||
));
|
||||
return OutputType::resolve(&self.node, &ctx_obj, ctx.item)
|
||||
.await
|
||||
.map(Some);
|
||||
} else if ctx.item.node.name.node == "cursor" {
|
||||
return Ok(Some(Value::String(self.cursor.encode_cursor())));
|
||||
}
|
||||
|
@ -140,7 +140,11 @@ where
|
|||
T: OutputType,
|
||||
E: ObjectType,
|
||||
{
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,12 +62,15 @@ impl ContainerType for EmptyMutation {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl OutputType for EmptyMutation {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
ctx.add_error(ServerError::new(
|
||||
async fn resolve(
|
||||
&self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
Err(ServerError::new(
|
||||
"Schema is not configured for mutations.",
|
||||
None,
|
||||
));
|
||||
Value::Null
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
9
src/types/external/cow.rs
vendored
9
src/types/external/cow.rs
vendored
|
@ -1,8 +1,9 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||
use async_graphql_parser::types::Field;
|
||||
|
||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, ServerResult, Type, Value};
|
||||
|
||||
impl<'a, T> Type for Cow<'a, T>
|
||||
where
|
||||
T: Type + ToOwned + ?Sized + Send + Sync,
|
||||
|
@ -22,7 +23,11 @@ where
|
|||
T: OutputType + ToOwned + ?Sized,
|
||||
<T as ToOwned>::Owned: Send + Sync,
|
||||
{
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
self.as_ref().resolve(ctx, field).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/btree_set.rs
vendored
8
src/types/external/list/btree_set.rs
vendored
|
@ -5,7 +5,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Type, Value,
|
||||
Positioned, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for BTreeSet<T> {
|
||||
|
@ -46,7 +46,11 @@ impl<T: InputType + Ord> InputType for BTreeSet<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + Ord> OutputType for BTreeSet<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self, Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/hash_set.rs
vendored
8
src/types/external/list/hash_set.rs
vendored
|
@ -7,7 +7,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Result, Type, Value,
|
||||
Positioned, Result, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for HashSet<T> {
|
||||
|
@ -48,7 +48,11 @@ impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + Hash + Eq> OutputType for HashSet<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self, Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/linked_list.rs
vendored
8
src/types/external/list/linked_list.rs
vendored
|
@ -5,7 +5,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Type, Value,
|
||||
Positioned, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for LinkedList<T> {
|
||||
|
@ -47,7 +47,11 @@ impl<T: InputType> InputType for LinkedList<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType> OutputType for LinkedList<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self, Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/slice.rs
vendored
8
src/types/external/list/slice.rs
vendored
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use crate::parser::types::Field;
|
||||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, ServerResult, Type, Value};
|
||||
|
||||
impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
|
@ -21,7 +21,11 @@ impl<'a, T: Type + 'a> Type for &'a [T] {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType> OutputType for &[T] {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self.iter(), Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/vec.rs
vendored
8
src/types/external/list/vec.rs
vendored
|
@ -4,7 +4,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Result, Type, Value,
|
||||
Positioned, Result, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for Vec<T> {
|
||||
|
@ -43,7 +43,11 @@ impl<T: InputType> InputType for Vec<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType> OutputType for Vec<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self, Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
8
src/types/external/list/vec_deque.rs
vendored
8
src/types/external/list/vec_deque.rs
vendored
|
@ -5,7 +5,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::resolve_list;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Type, Value,
|
||||
Positioned, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for VecDeque<T> {
|
||||
|
@ -47,7 +47,11 @@ impl<T: InputType> InputType for VecDeque<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType> OutputType for VecDeque<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self, Some(self.len())).await
|
||||
}
|
||||
}
|
||||
|
|
18
src/types/external/optional.rs
vendored
18
src/types/external/optional.rs
vendored
|
@ -3,7 +3,7 @@ use std::borrow::Cow;
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Type, Value,
|
||||
Positioned, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
impl<T: Type> Type for Option<T> {
|
||||
|
@ -41,11 +41,21 @@ impl<T: InputType> InputType for Option<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputType + Sync> OutputType for Option<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
if let Some(inner) = self {
|
||||
OutputType::resolve(inner, ctx, field).await
|
||||
match OutputType::resolve(inner, ctx, field).await {
|
||||
Ok(value) => Ok(value),
|
||||
Err(err) => {
|
||||
ctx.add_error(err);
|
||||
Ok(Value::Null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Value::Null
|
||||
Ok(Value::Null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
src/types/external/string.rs
vendored
10
src/types/external/string.rs
vendored
|
@ -3,7 +3,7 @@ use std::borrow::Cow;
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueError, InputValueResult, OutputType, Positioned,
|
||||
Scalar, ScalarType, Type, Value,
|
||||
Scalar, ScalarType, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
|
@ -37,7 +37,11 @@ impl Type for str {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl OutputType for str {
|
||||
async fn resolve(&self, _: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
Value::String(self.to_string())
|
||||
async fn resolve(
|
||||
&self,
|
||||
_: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
Ok(Value::String(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::parser::types::Field;
|
|||
use crate::registry::{MetaType, Registry};
|
||||
use crate::{
|
||||
from_value, to_value, ContextSelectionSet, InputValueError, InputValueResult, OutputType,
|
||||
Positioned, Scalar, ScalarType, Type, Value,
|
||||
Positioned, Scalar, ScalarType, ServerResult, Type, Value,
|
||||
};
|
||||
|
||||
/// A scalar that can represent any JSON value.
|
||||
|
@ -91,8 +91,12 @@ impl<T> Type for OutputJson<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: Serialize + Send + Sync> OutputType for OutputJson<T> {
|
||||
async fn resolve(&self, _ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
to_value(&self.0).ok().unwrap_or_default()
|
||||
async fn resolve(
|
||||
&self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
Ok(to_value(&self.0).ok().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,11 @@ where
|
|||
A: ObjectType,
|
||||
B: ObjectType,
|
||||
{
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,32 +95,30 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
|||
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
|
||||
if ctx.item.node.name.node == "__schema" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(
|
||||
&__Schema {
|
||||
registry: &ctx.schema_env.registry,
|
||||
},
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await,
|
||||
));
|
||||
return OutputType::resolve(
|
||||
&__Schema {
|
||||
registry: &ctx.schema_env.registry,
|
||||
},
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await
|
||||
.map(Some);
|
||||
} else if ctx.item.node.name.node == "__type" {
|
||||
let type_name: String = ctx.param_value("name", None)?;
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(
|
||||
&ctx.schema_env
|
||||
.registry
|
||||
.types
|
||||
.get(&type_name)
|
||||
.filter(|ty| ty.is_visible(ctx))
|
||||
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await,
|
||||
));
|
||||
return OutputType::resolve(
|
||||
&ctx.schema_env
|
||||
.registry
|
||||
.types
|
||||
.get(&type_name)
|
||||
.filter(|ty| ty.is_visible(ctx))
|
||||
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await
|
||||
.map(Some);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,16 +136,15 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
|||
return Ok(Some(Value::List(res)));
|
||||
} else if ctx.item.node.name.node == "_service" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return Ok(Some(
|
||||
OutputType::resolve(
|
||||
&Service {
|
||||
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
|
||||
},
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await,
|
||||
));
|
||||
return OutputType::resolve(
|
||||
&Service {
|
||||
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
|
||||
},
|
||||
&ctx_obj,
|
||||
ctx.item,
|
||||
)
|
||||
.await
|
||||
.map(Some);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +154,11 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: ObjectType> OutputType for QueryRoot<T> {
|
||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,7 @@ pub async fn test_error_extensions() {
|
|||
assert_eq!(
|
||||
serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(),
|
||||
serde_json::json!({
|
||||
"data": {
|
||||
"extendErr": null,
|
||||
},
|
||||
"data": null,
|
||||
"errors": [{
|
||||
"message": "my error",
|
||||
"locations": [{
|
||||
|
@ -51,9 +49,7 @@ pub async fn test_error_extensions() {
|
|||
assert_eq!(
|
||||
serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(),
|
||||
serde_json::json!({
|
||||
"data": {
|
||||
"extendResult": null,
|
||||
},
|
||||
"data": null,
|
||||
"errors": [{
|
||||
"message": "my error",
|
||||
"locations": [{
|
||||
|
|
126
tests/result.rs
126
tests/result.rs
|
@ -23,9 +23,12 @@ pub async fn test_fieldresult() {
|
|||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
|
||||
assert_eq!(
|
||||
schema.execute("{ error1:error error2:error }").await,
|
||||
schema.execute("{ error1:optError error2:optError }").await,
|
||||
Response {
|
||||
data: value!({ "error1": null, "error2": null }),
|
||||
data: value!({
|
||||
"error1": null,
|
||||
"error2": null,
|
||||
}),
|
||||
extensions: Default::default(),
|
||||
cache_control: Default::default(),
|
||||
errors: vec![
|
||||
|
@ -39,7 +42,7 @@ pub async fn test_fieldresult() {
|
|||
message: "TestError".to_string(),
|
||||
locations: vec![Pos {
|
||||
line: 1,
|
||||
column: 16,
|
||||
column: 19,
|
||||
}],
|
||||
path: vec![PathSegment::Field("error2".to_owned())],
|
||||
extensions: None,
|
||||
|
@ -133,3 +136,120 @@ pub async fn test_custom_error() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_error_propagation() {
|
||||
struct ParentObject;
|
||||
|
||||
#[Object]
|
||||
impl ParentObject {
|
||||
async fn child(&self) -> ChildObject {
|
||||
ChildObject
|
||||
}
|
||||
|
||||
async fn child_opt(&self) -> Option<ChildObject> {
|
||||
Some(ChildObject)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChildObject;
|
||||
|
||||
#[Object]
|
||||
impl ChildObject {
|
||||
async fn name(&self) -> Result<i32> {
|
||||
Err("myerror".into())
|
||||
}
|
||||
|
||||
async fn name_opt(&self) -> Option<Result<i32>> {
|
||||
Some(Err("myerror".into()))
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn parent(&self) -> ParentObject {
|
||||
ParentObject
|
||||
}
|
||||
|
||||
async fn parent_opt(&self) -> Option<ParentObject> {
|
||||
Some(ParentObject)
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
|
||||
assert_eq!(
|
||||
schema.execute("{ parent { child { name } } }").await,
|
||||
Response {
|
||||
data: Value::Null,
|
||||
extensions: Default::default(),
|
||||
cache_control: Default::default(),
|
||||
errors: vec![ServerError {
|
||||
message: "myerror".to_string(),
|
||||
locations: vec![Pos {
|
||||
line: 1,
|
||||
column: 20
|
||||
}],
|
||||
path: vec![
|
||||
PathSegment::Field("parent".to_owned()),
|
||||
PathSegment::Field("child".to_owned()),
|
||||
PathSegment::Field("name".to_owned()),
|
||||
],
|
||||
extensions: None,
|
||||
}],
|
||||
http_headers: Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema.execute("{ parent { childOpt { name } } }").await,
|
||||
Response {
|
||||
data: value!({
|
||||
"parent": {
|
||||
"childOpt": null,
|
||||
}
|
||||
}),
|
||||
extensions: Default::default(),
|
||||
cache_control: Default::default(),
|
||||
errors: vec![ServerError {
|
||||
message: "myerror".to_string(),
|
||||
locations: vec![Pos {
|
||||
line: 1,
|
||||
column: 23
|
||||
}],
|
||||
path: vec![
|
||||
PathSegment::Field("parent".to_owned()),
|
||||
PathSegment::Field("childOpt".to_owned()),
|
||||
PathSegment::Field("name".to_owned()),
|
||||
],
|
||||
extensions: None,
|
||||
}],
|
||||
http_headers: Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema.execute("{ parentOpt { child { name } } }").await,
|
||||
Response {
|
||||
data: value!({ "parentOpt": null }),
|
||||
extensions: Default::default(),
|
||||
cache_control: Default::default(),
|
||||
errors: vec![ServerError {
|
||||
message: "myerror".to_string(),
|
||||
locations: vec![Pos {
|
||||
line: 1,
|
||||
column: 23
|
||||
}],
|
||||
path: vec![
|
||||
PathSegment::Field("parentOpt".to_owned()),
|
||||
PathSegment::Field("child".to_owned()),
|
||||
PathSegment::Field("name".to_owned()),
|
||||
],
|
||||
extensions: None,
|
||||
}],
|
||||
http_headers: Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -385,7 +385,7 @@ pub async fn test_subscription_fieldresult() {
|
|||
}
|
||||
assert_eq!(
|
||||
Response {
|
||||
data: value!({ "values": null }),
|
||||
data: Value::Null,
|
||||
extensions: Default::default(),
|
||||
cache_control: Default::default(),
|
||||
errors: vec![ServerError {
|
||||
|
|
|
@ -262,9 +262,7 @@ pub async fn test_subscription_ws_transport_error() {
|
|||
"type": "next",
|
||||
"id": "1",
|
||||
"payload": {
|
||||
"data": {
|
||||
"events": { "value": null }
|
||||
},
|
||||
"data": null,
|
||||
"errors": [{
|
||||
"message": "TestError",
|
||||
"locations": [{"line": 1, "column": 25}],
|
||||
|
|
|
@ -258,7 +258,7 @@ pub async fn test_subscription_ws_transport_error() {
|
|||
"type": "data",
|
||||
"id": "1",
|
||||
"payload": {
|
||||
"data": { "events": { "value": null } },
|
||||
"data": null,
|
||||
"errors": [{
|
||||
"message": "TestError",
|
||||
"locations": [{"line": 1, "column": 25}],
|
||||
|
|
Loading…
Reference in New Issue
Block a user