Add support for returning multiple resolver errors. #531
This commit is contained in:
parent
88a16f9426
commit
f8021c0fb5
|
@ -311,7 +311,7 @@ pub fn generate(
|
||||||
let resolve_obj = quote! {
|
let resolve_obj = quote! {
|
||||||
{
|
{
|
||||||
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
||||||
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error().at(ctx.item.pos))?
|
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ pub fn generate(
|
||||||
let guard = guard.map(|guard| {
|
let guard = guard.map(|guard| {
|
||||||
quote! {
|
quote! {
|
||||||
#guard.check(ctx).await
|
#guard.check(ctx).await
|
||||||
.map_err(|err| err.into_server_error().at(ctx.item.pos))?;
|
.map_err(|err| err.into_server_error(ctx.item.pos))?;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ pub fn generate(
|
||||||
#guard
|
#guard
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
let res = #resolve_obj;
|
let res = #resolve_obj;
|
||||||
return #crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -162,8 +162,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
||||||
|
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #crate_name::OutputType for #ident {
|
impl #crate_name::OutputType for #ident {
|
||||||
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
::std::result::Result::Ok(#crate_name::resolver_utils::enum_value(*self))
|
#crate_name::resolver_utils::enum_value(*self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,14 +289,14 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
let resolve_obj = quote! {
|
let resolve_obj = quote! {
|
||||||
self.#method_name(#(#use_params),*)
|
self.#method_name(#(#use_params),*)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error().at(ctx.item.pos))?
|
.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))?
|
||||||
};
|
};
|
||||||
|
|
||||||
resolvers.push(quote! {
|
resolvers.push(quote! {
|
||||||
if ctx.item.node.name.node == #name {
|
if ctx.item.node.name.node == #name {
|
||||||
#(#get_params)*
|
#(#get_params)*
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return #crate_name::OutputType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#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)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,8 +103,8 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
|
||||||
&self,
|
&self,
|
||||||
_: &#crate_name::ContextSelectionSet<'_>,
|
_: &#crate_name::ContextSelectionSet<'_>,
|
||||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
) -> #crate_name::Value {
|
||||||
::std::result::Result::Ok(#crate_name::ScalarType::to_value(self))
|
#crate_name::ScalarType::to_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,7 +178,7 @@ pub fn generate(
|
||||||
// requires
|
// requires
|
||||||
requires_getter.push(quote! {
|
requires_getter.push(quote! {
|
||||||
let #ident: #ty = #crate_name::InputType::parse(params.get(#name).cloned()).
|
let #ident: #ty = #crate_name::InputType::parse(params.get(#name).cloned()).
|
||||||
map_err(|err| err.into_server_error().at(ctx.item.pos))?;
|
map_err(|err| err.into_server_error(ctx.item.pos))?;
|
||||||
});
|
});
|
||||||
use_keys.push(ident);
|
use_keys.push(ident);
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,11 @@ pub fn generate(
|
||||||
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> })
|
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> })
|
||||||
.expect("invalid result type");
|
.expect("invalid result type");
|
||||||
}
|
}
|
||||||
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().at(ctx.item.pos))? };
|
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))?
|
||||||
|
};
|
||||||
|
|
||||||
find_entities.push((
|
find_entities.push((
|
||||||
args.len(),
|
args.len(),
|
||||||
|
@ -219,7 +223,7 @@ pub fn generate(
|
||||||
if let (#(#key_pat),*) = (#(#key_getter),*) {
|
if let (#(#key_pat),*) = (#(#key_getter),*) {
|
||||||
#(#requires_getter)*
|
#(#requires_getter)*
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return #crate_name::OutputType::resolve(&#do_find, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&#do_find, &ctx_obj, ctx.item).await));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -513,7 +517,7 @@ pub fn generate(
|
||||||
let resolve_obj = quote! {
|
let resolve_obj = quote! {
|
||||||
{
|
{
|
||||||
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
||||||
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error().at(ctx.item.pos))?
|
res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -524,8 +528,7 @@ pub fn generate(
|
||||||
|
|
||||||
let guard = guard.map(|guard| {
|
let guard = guard.map(|guard| {
|
||||||
quote! {
|
quote! {
|
||||||
#guard.check(ctx).await
|
#guard.check(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?;
|
||||||
.map_err(|err| err.into_server_error().at(ctx.item.pos))?;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -536,7 +539,7 @@ pub fn generate(
|
||||||
#guard
|
#guard
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
let res = #resolve_obj;
|
let res = #resolve_obj;
|
||||||
return #crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -619,8 +622,7 @@ pub fn generate(
|
||||||
typename
|
typename
|
||||||
} else {
|
} else {
|
||||||
return ::std::result::Result::Err(
|
return ::std::result::Result::Err(
|
||||||
#crate_name::ServerError::new(r#""__typename" must be an existing string."#)
|
#crate_name::ServerError::new(r#""__typename" must be an existing string."#, ::std::option::Option::Some(ctx.item.pos))
|
||||||
.at(ctx.item.pos)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
#(#find_entities_iter)*
|
#(#find_entities_iter)*
|
||||||
|
@ -631,7 +633,7 @@ pub fn generate(
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #generics #crate_name::OutputType for #shadow_type<#generics_params> #where_clause {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,8 @@ pub fn generate(
|
||||||
&self,
|
&self,
|
||||||
_: &#crate_name::ContextSelectionSet<'_>,
|
_: &#crate_name::ContextSelectionSet<'_>,
|
||||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
) -> #crate_name::Value {
|
||||||
::std::result::Result::Ok(#crate_name::ScalarType::to_value(self))
|
#crate_name::ScalarType::to_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -100,7 +100,9 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
||||||
Some(meta) => generate_guards(&crate_name, &meta)?,
|
Some(meta) => generate_guards(&crate_name, &meta)?,
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let guard = guard.map(|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_server_error().at(ctx.item.pos))?; });
|
let guard = guard.map(
|
||||||
|
|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?; },
|
||||||
|
);
|
||||||
|
|
||||||
getters.push(if !field.owned {
|
getters.push(if !field.owned {
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -123,9 +125,9 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
||||||
resolvers.push(quote! {
|
resolvers.push(quote! {
|
||||||
if ctx.item.node.name.node == #field_name {
|
if ctx.item.node.name.node == #field_name {
|
||||||
#guard
|
#guard
|
||||||
let res = self.#ident(ctx).await.map_err(|err| err.into_server_error().at(ctx.item.pos))?;
|
let res = self.#ident(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?;
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return #crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
return ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -210,7 +212,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,7 +277,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #crate_name::OutputType for #concrete_type {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,9 +318,8 @@ pub fn generate(
|
||||||
self.#ident(ctx, #(#use_params),*)
|
self.#ident(ctx, #(#use_params),*)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
::std::convert::Into::<#crate_name::Error>::into(err).into_server_error()
|
::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos)
|
||||||
.at(ctx.item.pos)
|
.with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))])
|
||||||
.path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name)))
|
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -330,12 +329,10 @@ pub fn generate(
|
||||||
};
|
};
|
||||||
let guard = guard.map(|guard| quote! {
|
let guard = guard.map(|guard| quote! {
|
||||||
#guard.check(ctx).await.map_err(|err| {
|
#guard.check(ctx).await.map_err(|err| {
|
||||||
err.into_server_error()
|
err.into_server_error(ctx.item.pos)
|
||||||
.at(ctx.item.pos)
|
.with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))])
|
||||||
.path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name)))
|
|
||||||
})?;
|
})?;
|
||||||
});
|
});
|
||||||
|
|
||||||
let stream_fn = quote! {
|
let stream_fn = quote! {
|
||||||
let field_name = ::std::clone::Clone::clone(&ctx.item.node.response_key().node);
|
let field_name = ::std::clone::Clone::clone(&ctx.item.node.response_key().node);
|
||||||
let field = ::std::sync::Arc::new(::std::clone::Clone::clone(&ctx.item));
|
let field = ::std::sync::Arc::new(::std::clone::Clone::clone(&ctx.item));
|
||||||
|
@ -370,22 +367,21 @@ pub fn generate(
|
||||||
return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::Type>::qualified_type_name(),
|
return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::Type>::qualified_type_name(),
|
||||||
};
|
};
|
||||||
let resolve_fut = async {
|
let resolve_fut = async {
|
||||||
#crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field)
|
::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field).await))
|
||||||
.await
|
|
||||||
.map(::std::option::Option::Some)
|
|
||||||
};
|
};
|
||||||
#crate_name::futures_util::pin_mut!(resolve_fut);
|
#crate_name::futures_util::pin_mut!(resolve_fut);
|
||||||
query_env.extensions.resolve(ri, &mut resolve_fut).await
|
let mut resp = query_env.extensions.resolve(ri, &mut resolve_fut).await.map(|value| {
|
||||||
.map(|value| {
|
let mut map = ::std::collections::BTreeMap::new();
|
||||||
let mut map = ::std::collections::BTreeMap::new();
|
map.insert(::std::clone::Clone::clone(&field_name), value.unwrap_or_default());
|
||||||
map.insert(::std::clone::Clone::clone(&field_name), value.unwrap_or_default());
|
#crate_name::Response::new(#crate_name::Value::Object(map))
|
||||||
#crate_name::Response::new(#crate_name::Value::Object(map))
|
})
|
||||||
})
|
.unwrap_or_else(|err| {
|
||||||
.unwrap_or_else(|err| {
|
#crate_name::Response::from_errors(::std::vec![
|
||||||
#crate_name::Response::from_errors(::std::vec![
|
err.with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))])
|
||||||
err.path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name)))
|
])
|
||||||
])
|
});
|
||||||
})
|
resp.errors = ::std::mem::take(&mut query_env.errors.lock().unwrap());
|
||||||
|
resp
|
||||||
};
|
};
|
||||||
#crate_name::futures_util::pin_mut!(execute_fut);
|
#crate_name::futures_util::pin_mut!(execute_fut);
|
||||||
::std::result::Result::Ok(query_env.extensions.execute(&mut execute_fut).await)
|
::std::result::Result::Ok(query_env.extensions.execute(&mut execute_fut).await)
|
||||||
|
@ -474,5 +470,6 @@ pub fn generate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(expanded.into())
|
Ok(expanded.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
|
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::ServerResult<#crate_name::Value> {
|
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
#crate_name::resolver_utils::resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,15 @@ mod test_utils;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use async_graphql::*;
|
||||||
use reqwest::{header, StatusCode};
|
use reqwest::{header, StatusCode};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use async_graphql::*;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn quickstart() -> Result<()> {
|
async fn quickstart() -> Result<()> {
|
||||||
let listen_addr = test_utils::find_listen_addr();
|
let listen_addr = "127.0.0.1:8081";
|
||||||
|
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
struct QueryRoot;
|
struct QueryRoot;
|
||||||
|
@ -36,7 +35,7 @@ async fn quickstart() -> Result<()> {
|
||||||
let client = test_utils::client();
|
let client = test_utils::client();
|
||||||
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.post(listen_addr)
|
.post(&format!("http://{}", listen_addr))
|
||||||
.json(&json!({"query":"{ add(a: 10, b: 20) }"}))
|
.json(&json!({"query":"{ add(a: 10, b: 20) }"}))
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -48,7 +47,7 @@ async fn quickstart() -> Result<()> {
|
||||||
assert_eq!(string, json!({"data": {"add": 30}}).to_string());
|
assert_eq!(string, json!({"data": {"add": 30}}).to_string());
|
||||||
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.get(listen_addr)
|
.get(&format!("http://{}", listen_addr))
|
||||||
.query(&[("query", "{ add(a: 10, b: 20) }")])
|
.query(&[("query", "{ add(a: 10, b: 20) }")])
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -64,7 +63,7 @@ async fn quickstart() -> Result<()> {
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn hello() -> Result<()> {
|
async fn hello() -> Result<()> {
|
||||||
let listen_addr = test_utils::find_listen_addr();
|
let listen_addr = "127.0.0.1:8082";
|
||||||
|
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
struct Hello(String);
|
struct Hello(String);
|
||||||
|
@ -104,7 +103,7 @@ async fn hello() -> Result<()> {
|
||||||
let client = test_utils::client();
|
let client = test_utils::client();
|
||||||
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.post(listen_addr)
|
.post(&format!("http://{}", listen_addr))
|
||||||
.json(&json!({"query":"{ hello }"}))
|
.json(&json!({"query":"{ hello }"}))
|
||||||
.header("Name", "Foo")
|
.header("Name", "Foo")
|
||||||
.send()
|
.send()
|
||||||
|
@ -117,7 +116,7 @@ async fn hello() -> Result<()> {
|
||||||
assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string());
|
assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string());
|
||||||
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.post(listen_addr)
|
.post(&format!("http://{}", listen_addr))
|
||||||
.json(&json!({"query":"{ hello }"}))
|
.json(&json!({"query":"{ hello }"}))
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.header(header::CONTENT_TYPE, "application/json")
|
||||||
.send()
|
.send()
|
||||||
|
@ -137,7 +136,7 @@ async fn hello() -> Result<()> {
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn upload() -> Result<()> {
|
async fn upload() -> Result<()> {
|
||||||
let listen_addr = test_utils::find_listen_addr();
|
let listen_addr = "127.0.0.1:8083";
|
||||||
|
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
struct QueryRoot;
|
struct QueryRoot;
|
||||||
|
@ -198,7 +197,11 @@ async fn upload() -> Result<()> {
|
||||||
.text("map", r#"{ "0": ["variables.file"] }"#)
|
.text("map", r#"{ "0": ["variables.file"] }"#)
|
||||||
.part("0", reqwest::multipart::Part::stream("test").file_name("test.txt").mime_str("text/plain")?);
|
.part("0", reqwest::multipart::Part::stream("test").file_name("test.txt").mime_str("text/plain")?);
|
||||||
|
|
||||||
let resp = client.post(listen_addr).multipart(form).send().await?;
|
let resp = client
|
||||||
|
.post(&format!("http://{}", listen_addr))
|
||||||
|
.multipart(form)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
let string = resp.text().await?;
|
let string = resp.text().await?;
|
||||||
|
|
|
@ -1,19 +1,6 @@
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn find_listen_addr() -> &'static str {
|
|
||||||
Box::leak(
|
|
||||||
format!(
|
|
||||||
"http://{}",
|
|
||||||
std::net::TcpListener::bind("localhost:0")
|
|
||||||
.unwrap()
|
|
||||||
.local_addr()
|
|
||||||
.unwrap()
|
|
||||||
)
|
|
||||||
.into_boxed_str(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client() -> Client {
|
pub fn client() -> Client {
|
||||||
Client::builder().no_proxy().build().unwrap()
|
Client::builder().no_proxy().build().unwrap()
|
||||||
}
|
}
|
||||||
|
|
37
src/base.rs
37
src/base.rs
|
@ -57,11 +57,7 @@ pub trait InputType: Type + Send + Sync + Sized {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait OutputType: Type + Send + Sync {
|
pub trait OutputType: Type + Send + Sync {
|
||||||
/// Resolve an output value to `async_graphql::Value`.
|
/// Resolve an output value to `async_graphql::Value`.
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value;
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type + ?Sized> Type for &T {
|
impl<T: Type + ?Sized> Type for &T {
|
||||||
|
@ -77,11 +73,7 @@ impl<T: Type + ?Sized> Type for &T {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + ?Sized> OutputType for &T {
|
impl<T: OutputType + ?Sized> OutputType for &T {
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
T::resolve(*self, ctx, field).await
|
T::resolve(*self, ctx, field).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,14 +94,13 @@ impl<T: Type, E: Into<Error> + Send + Sync + Clone> Type for Result<T, E> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for Result<T, E> {
|
impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for Result<T, E> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
match self {
|
match self {
|
||||||
Ok(value) => Ok(value.resolve(ctx, field).await?),
|
Ok(value) => value.resolve(ctx, field).await,
|
||||||
Err(err) => Err(err.clone().into().into_server_error().at(field.pos)),
|
Err(err) => {
|
||||||
|
ctx.add_error(err.clone().into().into_server_error(field.pos));
|
||||||
|
Value::Null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,11 +133,7 @@ impl<T: Type + ?Sized> Type for Box<T> {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
T::resolve(&**self, ctx, field).await
|
T::resolve(&**self, ctx, field).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,11 +164,7 @@ impl<T: Type + ?Sized> Type for Arc<T> {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + ?Sized> OutputType for Arc<T> {
|
impl<T: OutputType + ?Sized> OutputType for Arc<T> {
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
T::resolve(&**self, ctx, field).await
|
T::resolve(&**self, ctx, field).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ use crate::parser::types::{
|
||||||
};
|
};
|
||||||
use crate::schema::SchemaEnv;
|
use crate::schema::SchemaEnv;
|
||||||
use crate::{
|
use crate::{
|
||||||
Error, InputType, Lookahead, Name, Pos, Positioned, Result, ServerError, ServerResult,
|
Error, InputType, Lookahead, Name, PathSegment, Pos, Positioned, Result, ServerError,
|
||||||
UploadValue, Value,
|
ServerResult, UploadValue, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Schema/Context data.
|
/// Schema/Context data.
|
||||||
|
@ -214,6 +214,7 @@ pub struct QueryEnvInner {
|
||||||
pub ctx_data: Arc<Data>,
|
pub ctx_data: Arc<Data>,
|
||||||
pub http_headers: Mutex<HeaderMap<String>>,
|
pub http_headers: Mutex<HeaderMap<String>>,
|
||||||
pub disable_introspection: bool,
|
pub disable_introspection: bool,
|
||||||
|
pub errors: Mutex<Vec<ServerError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -280,6 +281,33 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the global data defined in the `Context` or `Schema`.
|
/// Gets the global data defined in the `Context` or `Schema`.
|
||||||
///
|
///
|
||||||
/// If both `Schema` and `Query` have the same data type, the data in the `Query` is obtained.
|
/// If both `Schema` and `Query` have the same data type, the data in the `Query` is obtained.
|
||||||
|
@ -457,7 +485,9 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
.or_else(|| def.node.default_value())
|
.or_else(|| def.node.default_value())
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| ServerError::new(format!("Variable {} is not defined.", name)).at(pos))
|
.ok_or_else(|| {
|
||||||
|
ServerError::new(format!("Variable {} is not defined.", name), Some(pos))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_input_value(&self, value: Positioned<InputValue>) -> ServerResult<Value> {
|
fn resolve_input_value(&self, value: Positioned<InputValue>) -> ServerResult<Value> {
|
||||||
|
@ -486,7 +516,7 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
let condition_input = directive
|
let condition_input = directive
|
||||||
.node
|
.node
|
||||||
.get_argument("if")
|
.get_argument("if")
|
||||||
.ok_or_else(|| ServerError::new(format!(r#"Directive @{} requires argument `if` of type `Boolean!` but it was not provided."#, if include { "include" } else { "skip" })).at(directive.pos))?
|
.ok_or_else(|| ServerError::new(format!(r#"Directive @{} requires argument `if` of type `Boolean!` but it was not provided."#, if include { "include" } else { "skip" }),Some(directive.pos)))?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let pos = condition_input.pos;
|
let pos = condition_input.pos;
|
||||||
|
@ -494,7 +524,7 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
|
|
||||||
if include
|
if include
|
||||||
!= <bool as InputType>::parse(Some(condition_input))
|
!= <bool as InputType>::parse(Some(condition_input))
|
||||||
.map_err(|e| e.into_server_error().at(pos))?
|
.map_err(|e| e.into_server_error(pos))?
|
||||||
{
|
{
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
@ -536,7 +566,7 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
||||||
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
|
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
|
||||||
None => (Pos::default(), None),
|
None => (Pos::default(), None),
|
||||||
};
|
};
|
||||||
InputType::parse(value).map_err(|e| e.into_server_error().at(pos))
|
InputType::parse(value).map_err(|e| e.into_server_error(pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a uniform interface to inspect the forthcoming selections.
|
/// Creates a uniform interface to inspect the forthcoming selections.
|
||||||
|
|
31
src/error.rs
31
src/error.rs
|
@ -41,25 +41,18 @@ fn error_extensions_is_empty(values: &Option<ErrorExtensionValues>) -> bool {
|
||||||
|
|
||||||
impl ServerError {
|
impl ServerError {
|
||||||
/// Create a new server error with the message.
|
/// Create a new server error with the message.
|
||||||
pub fn new(message: impl Into<String>) -> Self {
|
pub fn new(message: impl Into<String>, pos: Option<Pos>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
locations: Vec::new(),
|
locations: pos.map(|pos| vec![pos]).unwrap_or_default(),
|
||||||
path: Vec::new(),
|
path: Vec::new(),
|
||||||
extensions: None,
|
extensions: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a position to the error.
|
#[doc(hidden)]
|
||||||
pub fn at(mut self, at: Pos) -> Self {
|
pub fn with_path(self, path: Vec<PathSegment>) -> Self {
|
||||||
self.locations.push(at);
|
Self { path, ..self }
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepend a path to the error.
|
|
||||||
pub fn path(mut self, path: PathSegment) -> Self {
|
|
||||||
self.path.insert(0, path);
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,12 +68,6 @@ impl From<ServerError> for Vec<ServerError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for ServerError {
|
|
||||||
fn from(e: Error) -> Self {
|
|
||||||
e.into_server_error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<parser::Error> for ServerError {
|
impl From<parser::Error> for ServerError {
|
||||||
fn from(e: parser::Error) -> Self {
|
fn from(e: parser::Error) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -158,8 +145,8 @@ impl<T: InputType> InputValueError<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the error into a server error.
|
/// Convert the error into a server error.
|
||||||
pub fn into_server_error(self) -> ServerError {
|
pub fn into_server_error(self, pos: Pos) -> ServerError {
|
||||||
ServerError::new(self.message)
|
ServerError::new(self.message, Some(pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,10 +180,10 @@ impl Error {
|
||||||
|
|
||||||
/// Convert the error to a server error.
|
/// Convert the error to a server error.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_server_error(self) -> ServerError {
|
pub fn into_server_error(self, pos: Pos) -> ServerError {
|
||||||
ServerError {
|
ServerError {
|
||||||
message: self.message,
|
message: self.message,
|
||||||
locations: Vec::new(),
|
locations: vec![pos],
|
||||||
path: Vec::new(),
|
path: Vec::new(),
|
||||||
extensions: self.extensions,
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,11 +85,11 @@ impl<T: CacheStorage> Extension for ApolloPersistedQueriesExtension<T> {
|
||||||
) -> ServerResult<Request> {
|
) -> ServerResult<Request> {
|
||||||
let res = if let Some(value) = request.extensions.remove("persistedQuery") {
|
let res = if let Some(value) = request.extensions.remove("persistedQuery") {
|
||||||
let persisted_query: PersistedQuery = from_value(value).map_err(|_| {
|
let persisted_query: PersistedQuery = from_value(value).map_err(|_| {
|
||||||
ServerError::new("Invalid \"PersistedQuery\" extension configuration.")
|
ServerError::new("Invalid \"PersistedQuery\" extension configuration.", None)
|
||||||
})?;
|
})?;
|
||||||
if persisted_query.version != 1 {
|
if persisted_query.version != 1 {
|
||||||
return Err(ServerError::new(
|
return Err(ServerError::new(
|
||||||
format!("Only the \"PersistedQuery\" extension of version \"1\" is supported, and the current version is \"{}\".", persisted_query.version),
|
format!("Only the \"PersistedQuery\" extension of version \"1\" is supported, and the current version is \"{}\".", persisted_query.version), None
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,13 +97,13 @@ impl<T: CacheStorage> Extension for ApolloPersistedQueriesExtension<T> {
|
||||||
if let Some(query) = self.storage.get(persisted_query.sha256_hash).await {
|
if let Some(query) = self.storage.get(persisted_query.sha256_hash).await {
|
||||||
Ok(Request { query, ..request })
|
Ok(Request { query, ..request })
|
||||||
} else {
|
} else {
|
||||||
Err(ServerError::new("PersistedQueryNotFound".to_string()))
|
Err(ServerError::new("PersistedQueryNotFound", None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let sha256_hash = format!("{:x}", Sha256::digest(request.query.as_bytes()));
|
let sha256_hash = format!("{:x}", Sha256::digest(request.query.as_bytes()));
|
||||||
|
|
||||||
if persisted_query.sha256_hash != sha256_hash {
|
if persisted_query.sha256_hash != sha256_hash {
|
||||||
Err(ServerError::new("provided sha does not match query"))
|
Err(ServerError::new("provided sha does not match query", None))
|
||||||
} else {
|
} else {
|
||||||
self.storage.set(sha256_hash, request.query.clone()).await;
|
self.storage.set(sha256_hash, request.query.clone()).await;
|
||||||
Ok(request)
|
Ok(request)
|
||||||
|
@ -179,7 +179,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
schema.execute(request).await.into_result().unwrap_err(),
|
schema.execute(request).await.into_result().unwrap_err(),
|
||||||
vec![ServerError::new("PersistedQueryNotFound")]
|
vec![ServerError::new("PersistedQueryNotFound", None)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ use std::pin::Pin;
|
||||||
use crate::extensions::ResolveInfo;
|
use crate::extensions::ResolveInfo;
|
||||||
use crate::parser::types::Selection;
|
use crate::parser::types::Selection;
|
||||||
use crate::registry::MetaType;
|
use crate::registry::MetaType;
|
||||||
use crate::{
|
use crate::{Context, ContextSelectionSet, Name, OutputType, ServerError, ServerResult, Value};
|
||||||
Context, ContextSelectionSet, Name, OutputType, PathSegment, ServerError, ServerResult, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents a GraphQL container object.
|
/// Represents a GraphQL container object.
|
||||||
///
|
///
|
||||||
|
@ -64,7 +62,7 @@ impl<T: ContainerType> ContainerType for &T {
|
||||||
pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
|
pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
|
||||||
ctx: &ContextSelectionSet<'a>,
|
ctx: &ContextSelectionSet<'a>,
|
||||||
root: &'a T,
|
root: &'a T,
|
||||||
) -> ServerResult<Value> {
|
) -> Value {
|
||||||
resolve_container_inner(ctx, root, true).await
|
resolve_container_inner(ctx, root, true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +70,7 @@ pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
|
||||||
pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>(
|
pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>(
|
||||||
ctx: &ContextSelectionSet<'a>,
|
ctx: &ContextSelectionSet<'a>,
|
||||||
root: &'a T,
|
root: &'a T,
|
||||||
) -> ServerResult<Value> {
|
) -> Value {
|
||||||
resolve_container_inner(ctx, root, false).await
|
resolve_container_inner(ctx, root, false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,16 +104,20 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>(
|
||||||
ctx: &ContextSelectionSet<'a>,
|
ctx: &ContextSelectionSet<'a>,
|
||||||
root: &'a T,
|
root: &'a T,
|
||||||
parallel: bool,
|
parallel: bool,
|
||||||
) -> ServerResult<Value> {
|
) -> Value {
|
||||||
let mut fields = Fields(Vec::new());
|
let mut fields = Fields(Vec::new());
|
||||||
fields.add_set(ctx, root)?;
|
|
||||||
|
if let Err(err) = fields.add_set(ctx, root) {
|
||||||
|
ctx.add_error(err);
|
||||||
|
return Value::Null;
|
||||||
|
}
|
||||||
|
|
||||||
let res = if parallel {
|
let res = if parallel {
|
||||||
futures_util::future::try_join_all(fields.0).await?
|
futures_util::future::join_all(fields.0).await
|
||||||
} else {
|
} else {
|
||||||
let mut results = Vec::with_capacity(fields.0.len());
|
let mut results = Vec::with_capacity(fields.0.len());
|
||||||
for field in fields.0 {
|
for field in fields.0 {
|
||||||
results.push(field.await?);
|
results.push(field.await);
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
};
|
};
|
||||||
|
@ -124,10 +126,10 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>(
|
||||||
for (name, value) in res {
|
for (name, value) in res {
|
||||||
insert_value(&mut map, name, value);
|
insert_value(&mut map, name, value);
|
||||||
}
|
}
|
||||||
Ok(Value::Object(map))
|
Value::Object(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;
|
type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = (Name, Value)> + 'a + Send>>;
|
||||||
|
|
||||||
/// A set of fields on an container that are being selected.
|
/// A set of fields on an container that are being selected.
|
||||||
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
||||||
|
@ -152,9 +154,9 @@ impl<'a> Fields<'a> {
|
||||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||||
let typename = root.introspection_type_name().into_owned();
|
let typename = root.introspection_type_name().into_owned();
|
||||||
|
|
||||||
self.0.push(Box::pin(async move {
|
self.0.push(Box::pin(
|
||||||
Ok((field_name, Value::String(typename)))
|
async move { (field_name, Value::String(typename)) },
|
||||||
}));
|
));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,9 +179,10 @@ impl<'a> Fields<'a> {
|
||||||
|
|
||||||
if extensions.is_empty() {
|
if extensions.is_empty() {
|
||||||
match root.resolve_field(&ctx_field).await {
|
match root.resolve_field(&ctx_field).await {
|
||||||
Ok(value) => Ok((field_name, value.unwrap_or_default())),
|
Ok(value) => (field_name, value.unwrap_or_default()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Err(e.path(PathSegment::Field(field_name.to_string())))
|
ctx_field.add_error(e);
|
||||||
|
(field_name, Value::Null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -199,23 +202,26 @@ impl<'a> Fields<'a> {
|
||||||
{
|
{
|
||||||
Some(ty) => &ty,
|
Some(ty) => &ty,
|
||||||
None => {
|
None => {
|
||||||
return Err(ServerError::new(format!(
|
ctx_field.add_error(ServerError::new(
|
||||||
r#"Cannot query field "{}" on type "{}"."#,
|
format!(
|
||||||
field_name, type_name
|
r#"Cannot query field "{}" on type "{}"."#,
|
||||||
))
|
field_name, type_name
|
||||||
.at(ctx_field.item.pos)
|
),
|
||||||
.path(PathSegment::Field(field_name.to_string())));
|
Some(ctx_field.item.pos),
|
||||||
|
));
|
||||||
|
return (field_name, Value::Null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolve_fut = async { root.resolve_field(&ctx_field).await };
|
let resolve_fut = root.resolve_field(&ctx_field);
|
||||||
futures_util::pin_mut!(resolve_fut);
|
futures_util::pin_mut!(resolve_fut);
|
||||||
let res = extensions.resolve(resolve_info, &mut resolve_fut).await;
|
let res = extensions.resolve(resolve_info, &mut resolve_fut).await;
|
||||||
match res {
|
match res {
|
||||||
Ok(value) => Ok((field_name, value.unwrap_or_default())),
|
Ok(value) => (field_name, value.unwrap_or_default()),
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
Err(e.path(PathSegment::Field(field_name.to_string())))
|
ctx.add_error(err);
|
||||||
|
(field_name, Value::Null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,11 +237,13 @@ impl<'a> Fields<'a> {
|
||||||
let fragment = match fragment {
|
let fragment = match fragment {
|
||||||
Some(fragment) => fragment,
|
Some(fragment) => fragment,
|
||||||
None => {
|
None => {
|
||||||
return Err(ServerError::new(format!(
|
return Err(ServerError::new(
|
||||||
r#"Unknown fragment "{}"."#,
|
format!(
|
||||||
spread.node.fragment_name.node
|
r#"Unknown fragment "{}"."#,
|
||||||
))
|
spread.node.fragment_name.node
|
||||||
.at(spread.pos));
|
),
|
||||||
|
Some(spread.pos),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::extensions::ResolveInfo;
|
use crate::extensions::ResolveInfo;
|
||||||
use crate::parser::types::Field;
|
use crate::parser::types::Field;
|
||||||
use crate::{ContextSelectionSet, OutputType, PathSegment, Positioned, ServerResult, Type, Value};
|
use crate::{ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||||
|
|
||||||
/// Resolve an list by executing each of the items concurrently.
|
/// Resolve an list by executing each of the items concurrently.
|
||||||
pub async fn resolve_list<'a, T: OutputType + 'a>(
|
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>,
|
field: &Positioned<Field>,
|
||||||
iter: impl IntoIterator<Item = T>,
|
iter: impl IntoIterator<Item = T>,
|
||||||
len: Option<usize>,
|
len: Option<usize>,
|
||||||
) -> ServerResult<Value> {
|
) -> Value {
|
||||||
let extensions = &ctx.query_env.extensions;
|
let extensions = &ctx.query_env.extensions;
|
||||||
if !extensions.is_empty() {
|
if !extensions.is_empty() {
|
||||||
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
||||||
|
@ -24,35 +24,24 @@ pub async fn resolve_list<'a, T: OutputType + 'a>(
|
||||||
parent_type: &Vec::<T>::type_name(),
|
parent_type: &Vec::<T>::type_name(),
|
||||||
return_type: &T::qualified_type_name(),
|
return_type: &T::qualified_type_name(),
|
||||||
};
|
};
|
||||||
let resolve_fut = async {
|
let resolve_fut =
|
||||||
OutputType::resolve(&item, &ctx_idx, field)
|
async { Ok(Some(OutputType::resolve(&item, &ctx_idx, field).await)) };
|
||||||
.await
|
|
||||||
.map(Option::Some)
|
|
||||||
.map_err(|e| e.path(PathSegment::Index(idx)))
|
|
||||||
};
|
|
||||||
futures_util::pin_mut!(resolve_fut);
|
futures_util::pin_mut!(resolve_fut);
|
||||||
extensions
|
extensions
|
||||||
.resolve(resolve_info, &mut resolve_fut)
|
.resolve(resolve_info, &mut resolve_fut)
|
||||||
.await
|
.await
|
||||||
.map(|value| value.expect("You definitely encountered a bug!"))
|
.unwrap()
|
||||||
|
.expect("You definitely encountered a bug!")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(Value::List(
|
Value::List(futures_util::future::join_all(futures).await)
|
||||||
futures_util::future::try_join_all(futures).await?,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
let mut futures = len.map(Vec::with_capacity).unwrap_or_default();
|
||||||
for (idx, item) in iter.into_iter().enumerate() {
|
for (idx, item) in iter.into_iter().enumerate() {
|
||||||
let ctx_idx = ctx.with_index(idx);
|
let ctx_idx = ctx.with_index(idx);
|
||||||
futures.push(async move {
|
futures.push(async move { OutputType::resolve(&item, &ctx_idx, field).await });
|
||||||
OutputType::resolve(&item, &ctx_idx, field)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.path(PathSegment::Index(idx)))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Ok(Value::List(
|
Value::List(futures_util::future::join_all(futures).await)
|
||||||
futures_util::future::try_join_all(futures).await?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,8 @@ macro_rules! scalar_internal {
|
||||||
&self,
|
&self,
|
||||||
_: &$crate::ContextSelectionSet<'_>,
|
_: &$crate::ContextSelectionSet<'_>,
|
||||||
_field: &$crate::Positioned<$crate::parser::types::Field>,
|
_field: &$crate::Positioned<$crate::parser::types::Field>,
|
||||||
) -> $crate::ServerResult<$crate::Value> {
|
) -> $crate::Value {
|
||||||
::std::result::Result::Ok($crate::ScalarType::to_value(self))
|
$crate::ScalarType::to_value(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{CacheControl, Result, ServerError, Value};
|
use crate::{CacheControl, Result, ServerError, Value};
|
||||||
|
|
||||||
/// Query response
|
/// Query response
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
/// Data of query result
|
/// Data of query result
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
@ -404,13 +404,13 @@ where
|
||||||
// check limit
|
// check limit
|
||||||
if let Some(limit_complexity) = self.complexity {
|
if let Some(limit_complexity) = self.complexity {
|
||||||
if validation_result.complexity > limit_complexity {
|
if validation_result.complexity > limit_complexity {
|
||||||
return Err(vec![ServerError::new("Query is too complex.")]);
|
return Err(vec![ServerError::new("Query is too complex.", None)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(limit_depth) = self.depth {
|
if let Some(limit_depth) = self.depth {
|
||||||
if validation_result.depth > limit_depth {
|
if validation_result.depth > limit_depth {
|
||||||
return Err(vec![ServerError::new("Query is nested too deep.")]);
|
return Err(vec![ServerError::new("Query is nested too deep.", None)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +422,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ServerError::new(format!(r#"Unknown operation named "{}""#, operation_name))
|
ServerError::new(
|
||||||
|
format!(r#"Unknown operation named "{}""#, operation_name),
|
||||||
|
None,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
match document.operations {
|
match document.operations {
|
||||||
|
@ -430,9 +433,10 @@ where
|
||||||
DocumentOperations::Multiple(map) if map.len() == 1 => {
|
DocumentOperations::Multiple(map) if map.len() == 1 => {
|
||||||
Ok(map.into_iter().next().unwrap().1)
|
Ok(map.into_iter().next().unwrap().1)
|
||||||
}
|
}
|
||||||
DocumentOperations::Multiple(_) => {
|
DocumentOperations::Multiple(_) => Err(ServerError::new(
|
||||||
Err(ServerError::new("Operation name required in request."))
|
"Operation name required in request.",
|
||||||
}
|
None,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let operation = match operation {
|
let operation = match operation {
|
||||||
|
@ -450,6 +454,7 @@ where
|
||||||
ctx_data: query_data,
|
ctx_data: query_data,
|
||||||
http_headers: Default::default(),
|
http_headers: Default::default(),
|
||||||
disable_introspection: request.disable_introspection,
|
disable_introspection: request.disable_introspection,
|
||||||
|
errors: Default::default(),
|
||||||
};
|
};
|
||||||
Ok((QueryEnv::new(env), validation_result.cache_control))
|
Ok((QueryEnv::new(env), validation_result.cache_control))
|
||||||
}
|
}
|
||||||
|
@ -463,23 +468,21 @@ where
|
||||||
query_env: &env,
|
query_env: &env,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = match &env.operation.node.ty {
|
let value = match &env.operation.node.ty {
|
||||||
OperationType::Query => resolve_container(&ctx, &self.query).await,
|
OperationType::Query => resolve_container(&ctx, &self.query).await,
|
||||||
OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await,
|
OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await,
|
||||||
OperationType::Subscription => {
|
OperationType::Subscription => {
|
||||||
return Response::from_errors(vec![ServerError::new(
|
return Response::from_errors(vec![ServerError::new(
|
||||||
"Subscriptions are not supported on this transport.",
|
"Subscriptions are not supported on this transport.",
|
||||||
|
None,
|
||||||
)]);
|
)]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
let mut resp = Response::new(value)
|
||||||
Ok(data) => {
|
.http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap()));
|
||||||
let resp = Response::new(data);
|
resp.errors = std::mem::take(&mut env.errors.lock().unwrap());
|
||||||
resp.http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap()))
|
resp
|
||||||
}
|
|
||||||
Err(err) => Response::from_errors(vec![err]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a GraphQL query.
|
/// Execute a GraphQL query.
|
||||||
|
|
|
@ -43,9 +43,8 @@ pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + 'static>(
|
||||||
yield resp;
|
yield resp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let err = ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()))
|
let err = ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()), Some(ctx.item.pos))
|
||||||
.at(ctx.item.pos)
|
.with_path(vec![PathSegment::Field(field_name.to_string())]);
|
||||||
.path(PathSegment::Field(field_name.to_string()));
|
|
||||||
yield Response::from_errors(vec![err]);
|
yield Response::from_errors(vec![err]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,14 +212,14 @@ where
|
||||||
end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()),
|
end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()),
|
||||||
};
|
};
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(&page_info, &ctx_obj, ctx.item)
|
return Ok(Some(
|
||||||
.await
|
OutputType::resolve(&page_info, &ctx_obj, ctx.item).await,
|
||||||
.map(Some);
|
));
|
||||||
} else if ctx.item.node.name.node == "edges" {
|
} else if ctx.item.node.name.node == "edges" {
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(&self.edges, &ctx_obj, ctx.item)
|
return Ok(Some(
|
||||||
.await
|
OutputType::resolve(&self.edges, &ctx_obj, ctx.item).await,
|
||||||
.map(Some);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.additional_fields.resolve_field(ctx).await
|
self.additional_fields.resolve_field(ctx).await
|
||||||
|
@ -234,11 +234,7 @@ where
|
||||||
EC: ObjectType,
|
EC: ObjectType,
|
||||||
EE: ObjectType,
|
EE: ObjectType,
|
||||||
{
|
{
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
_field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_container(ctx, self).await
|
resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,9 +122,9 @@ where
|
||||||
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
||||||
if ctx.item.node.name.node == "node" {
|
if ctx.item.node.name.node == "node" {
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(&self.node, &ctx_obj, ctx.item)
|
return Ok(Some(
|
||||||
.await
|
OutputType::resolve(&self.node, &ctx_obj, ctx.item).await,
|
||||||
.map(Some);
|
));
|
||||||
} else if ctx.item.node.name.node == "cursor" {
|
} else if ctx.item.node.name.node == "cursor" {
|
||||||
return Ok(Some(Value::String(self.cursor.encode_cursor())));
|
return Ok(Some(Value::String(self.cursor.encode_cursor())));
|
||||||
}
|
}
|
||||||
|
@ -140,11 +140,7 @@ where
|
||||||
T: OutputType,
|
T: OutputType,
|
||||||
E: ObjectType,
|
E: ObjectType,
|
||||||
{
|
{
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
_field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_container(ctx, self).await
|
resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,12 +62,12 @@ impl ContainerType for EmptyMutation {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl OutputType for EmptyMutation {
|
impl OutputType for EmptyMutation {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
ctx.add_error(ServerError::new(
|
||||||
_ctx: &ContextSelectionSet<'_>,
|
"Schema is not configured for mutations.",
|
||||||
field: &Positioned<Field>,
|
None,
|
||||||
) -> ServerResult<Value> {
|
));
|
||||||
Err(ServerError::new("Schema is not configured for mutations.").at(field.pos))
|
Value::Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,13 +36,13 @@ impl SubscriptionType for EmptySubscription {
|
||||||
|
|
||||||
fn create_field_stream<'a>(
|
fn create_field_stream<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctx: &'a Context<'_>,
|
_ctx: &'a Context<'_>,
|
||||||
) -> Option<Pin<Box<dyn Stream<Item = Response> + Send + 'a>>>
|
) -> Option<Pin<Box<dyn Stream<Item = Response> + Send + 'a>>>
|
||||||
where
|
where
|
||||||
Self: Send + Sync + 'static + Sized,
|
Self: Send + Sync + 'static + Sized,
|
||||||
{
|
{
|
||||||
Some(Box::pin(stream::once(async move {
|
Some(Box::pin(stream::once(async move {
|
||||||
let err = ServerError::new("Schema is not configured for mutations.").at(ctx.item.pos);
|
let err = ServerError::new("Schema is not configured for subscription.", None);
|
||||||
Response::from_errors(vec![err])
|
Response::from_errors(vec![err])
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
8
src/types/external/cow.rs
vendored
8
src/types/external/cow.rs
vendored
|
@ -1,6 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, ServerResult, Type, Value};
|
use crate::{registry, ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||||
use async_graphql_parser::types::Field;
|
use async_graphql_parser::types::Field;
|
||||||
|
|
||||||
impl<'a, T> Type for Cow<'a, T>
|
impl<'a, T> Type for Cow<'a, T>
|
||||||
|
@ -22,11 +22,7 @@ where
|
||||||
T: OutputType + ToOwned + ?Sized,
|
T: OutputType + ToOwned + ?Sized,
|
||||||
<T as ToOwned>::Owned: Send + Sync,
|
<T as ToOwned>::Owned: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
self.as_ref().resolve(ctx, field).await
|
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::resolver_utils::resolve_list;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, ServerResult, Type, Value,
|
Positioned, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for BTreeSet<T> {
|
impl<T: Type> Type for BTreeSet<T> {
|
||||||
|
@ -46,11 +46,7 @@ impl<T: InputType + Ord> InputType for BTreeSet<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + Ord> OutputType for BTreeSet<T> {
|
impl<T: OutputType + Ord> OutputType for BTreeSet<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self, Some(self.len())).await
|
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::resolver_utils::resolve_list;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, Result, ServerResult, Type, Value,
|
Positioned, Result, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for HashSet<T> {
|
impl<T: Type> Type for HashSet<T> {
|
||||||
|
@ -48,11 +48,7 @@ impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + Hash + Eq> OutputType for HashSet<T> {
|
impl<T: OutputType + Hash + Eq> OutputType for HashSet<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self, Some(self.len())).await
|
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::resolver_utils::resolve_list;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, ServerResult, Type, Value,
|
Positioned, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for LinkedList<T> {
|
impl<T: Type> Type for LinkedList<T> {
|
||||||
|
@ -47,11 +47,7 @@ impl<T: InputType> InputType for LinkedList<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType> OutputType for LinkedList<T> {
|
impl<T: OutputType> OutputType for LinkedList<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self, Some(self.len())).await
|
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::parser::types::Field;
|
||||||
use crate::resolver_utils::resolve_list;
|
use crate::resolver_utils::resolve_list;
|
||||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, ServerResult, Type, Value};
|
use crate::{registry, ContextSelectionSet, OutputType, Positioned, Type, Value};
|
||||||
|
|
||||||
impl<'a, T: Type + 'a> Type for &'a [T] {
|
impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
@ -21,11 +21,7 @@ impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType> OutputType for &[T] {
|
impl<T: OutputType> OutputType for &[T] {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self.iter(), Some(self.len())).await
|
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::resolver_utils::resolve_list;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, Result, ServerResult, Type, Value,
|
Positioned, Result, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for Vec<T> {
|
impl<T: Type> Type for Vec<T> {
|
||||||
|
@ -43,11 +43,7 @@ impl<T: InputType> InputType for Vec<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType> OutputType for Vec<T> {
|
impl<T: OutputType> OutputType for Vec<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self, Some(self.len())).await
|
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::resolver_utils::resolve_list;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, ServerResult, Type, Value,
|
Positioned, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for VecDeque<T> {
|
impl<T: Type> Type for VecDeque<T> {
|
||||||
|
@ -47,11 +47,7 @@ impl<T: InputType> InputType for VecDeque<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType> OutputType for VecDeque<T> {
|
impl<T: OutputType> OutputType for VecDeque<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_list(ctx, field, self, Some(self.len())).await
|
resolve_list(ctx, field, self, Some(self.len())).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/types/external/optional.rs
vendored
10
src/types/external/optional.rs
vendored
|
@ -3,7 +3,7 @@ use std::borrow::Cow;
|
||||||
use crate::parser::types::Field;
|
use crate::parser::types::Field;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, ServerResult, Type, Value,
|
Positioned, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: Type> Type for Option<T> {
|
impl<T: Type> Type for Option<T> {
|
||||||
|
@ -41,15 +41,11 @@ impl<T: InputType> InputType for Option<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + Sync> OutputType for Option<T> {
|
impl<T: OutputType + Sync> OutputType for Option<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
if let Some(inner) = self {
|
if let Some(inner) = self {
|
||||||
OutputType::resolve(inner, ctx, field).await
|
OutputType::resolve(inner, ctx, field).await
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::Null)
|
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::parser::types::Field;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextSelectionSet, InputValueError, InputValueResult, OutputType, Positioned,
|
registry, ContextSelectionSet, InputValueError, InputValueResult, OutputType, Positioned,
|
||||||
Scalar, ScalarType, ServerResult, Type, Value,
|
Scalar, ScalarType, 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.
|
/// 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,11 +37,7 @@ impl Type for str {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl OutputType for str {
|
impl OutputType for str {
|
||||||
async fn resolve(
|
async fn resolve(&self, _: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
Value::String(self.to_string())
|
||||||
_: &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::registry::{MetaType, Registry};
|
||||||
use crate::{
|
use crate::{
|
||||||
from_value, to_value, ContextSelectionSet, InputValueError, InputValueResult, OutputType,
|
from_value, to_value, ContextSelectionSet, InputValueError, InputValueResult, OutputType,
|
||||||
Positioned, Scalar, ScalarType, ServerResult, Type, Value,
|
Positioned, Scalar, ScalarType, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A scalar that can represent any JSON value.
|
/// A scalar that can represent any JSON value.
|
||||||
|
@ -91,12 +91,8 @@ impl<T> Type for OutputJson<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: Serialize + Send + Sync> OutputType for OutputJson<T> {
|
impl<T: Serialize + Send + Sync> OutputType for OutputJson<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, _ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
to_value(&self.0).ok().unwrap_or_default()
|
||||||
_ctx: &ContextSelectionSet<'_>,
|
|
||||||
_field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
Ok(to_value(&self.0).ok().unwrap_or_default())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,11 +90,7 @@ where
|
||||||
A: ObjectType,
|
A: ObjectType,
|
||||||
B: ObjectType,
|
B: ObjectType,
|
||||||
{
|
{
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
_field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_container(ctx, self).await
|
resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,30 +95,32 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
||||||
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
|
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
|
||||||
if ctx.item.node.name.node == "__schema" {
|
if ctx.item.node.name.node == "__schema" {
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(
|
return Ok(Some(
|
||||||
&__Schema {
|
OutputType::resolve(
|
||||||
registry: &ctx.schema_env.registry,
|
&__Schema {
|
||||||
},
|
registry: &ctx.schema_env.registry,
|
||||||
&ctx_obj,
|
},
|
||||||
ctx.item,
|
&ctx_obj,
|
||||||
)
|
ctx.item,
|
||||||
.await
|
)
|
||||||
.map(Some);
|
.await,
|
||||||
|
));
|
||||||
} else if ctx.item.node.name.node == "__type" {
|
} else if ctx.item.node.name.node == "__type" {
|
||||||
let type_name: String = ctx.param_value("name", None)?;
|
let type_name: String = ctx.param_value("name", None)?;
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(
|
return Ok(Some(
|
||||||
&ctx.schema_env
|
OutputType::resolve(
|
||||||
.registry
|
&ctx.schema_env
|
||||||
.types
|
.registry
|
||||||
.get(&type_name)
|
.types
|
||||||
.filter(|ty| ty.is_visible(ctx))
|
.get(&type_name)
|
||||||
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
|
.filter(|ty| ty.is_visible(ctx))
|
||||||
&ctx_obj,
|
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
|
||||||
ctx.item,
|
&ctx_obj,
|
||||||
)
|
ctx.item,
|
||||||
.await
|
)
|
||||||
.map(Some);
|
.await,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,25 +129,25 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
||||||
let representations: Vec<Any> = ctx.param_value("representations", None)?;
|
let representations: Vec<Any> = ctx.param_value("representations", None)?;
|
||||||
let res = futures_util::future::try_join_all(representations.iter().map(
|
let res = futures_util::future::try_join_all(representations.iter().map(
|
||||||
|item| async move {
|
|item| async move {
|
||||||
self.inner
|
self.inner.find_entity(ctx, &item.0).await?.ok_or_else(|| {
|
||||||
.find_entity(ctx, &item.0)
|
ServerError::new("Entity not found.", Some(ctx.item.pos))
|
||||||
.await?
|
})
|
||||||
.ok_or_else(|| ServerError::new("Entity not found.").at(ctx.item.pos))
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(Some(Value::List(res)));
|
return Ok(Some(Value::List(res)));
|
||||||
} else if ctx.item.node.name.node == "_service" {
|
} else if ctx.item.node.name.node == "_service" {
|
||||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||||
return OutputType::resolve(
|
return Ok(Some(
|
||||||
&Service {
|
OutputType::resolve(
|
||||||
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
|
&Service {
|
||||||
},
|
sdl: Some(ctx.schema_env.registry.export_sdl(true)),
|
||||||
&ctx_obj,
|
},
|
||||||
ctx.item,
|
&ctx_obj,
|
||||||
)
|
ctx.item,
|
||||||
.await
|
)
|
||||||
.map(Some);
|
.await,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,11 +157,7 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: ObjectType> OutputType for QueryRoot<T> {
|
impl<T: ObjectType> OutputType for QueryRoot<T> {
|
||||||
async fn resolve(
|
async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned<Field>) -> Value {
|
||||||
&self,
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
_field: &Positioned<Field>,
|
|
||||||
) -> ServerResult<Value> {
|
|
||||||
resolve_container(ctx, self).await
|
resolve_container(ctx, self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,8 +126,10 @@ impl<'a> VisitorContext<'a> {
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ServerError::new(format!("Variable {} is not defined.", name))
|
ServerError::new(
|
||||||
.at(pos)
|
format!("Variable {} is not defined.", name),
|
||||||
|
Some(pos),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})?),
|
})?),
|
||||||
)
|
)
|
||||||
|
@ -135,7 +137,7 @@ impl<'a> VisitorContext<'a> {
|
||||||
None => (Pos::default(), None),
|
None => (Pos::default(), None),
|
||||||
};
|
};
|
||||||
|
|
||||||
T::parse(value).map_err(|e| e.into_server_error().at(pos))
|
T::parse(value).map_err(|e| e.into_server_error(pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,9 @@ pub async fn test_error_extensions() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(),
|
serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(),
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"data": null,
|
"data": {
|
||||||
|
"extendErr": null,
|
||||||
|
},
|
||||||
"errors": [{
|
"errors": [{
|
||||||
"message": "my error",
|
"message": "my error",
|
||||||
"locations": [{
|
"locations": [{
|
||||||
|
@ -49,7 +51,9 @@ pub async fn test_error_extensions() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(),
|
serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(),
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"data": null,
|
"data": {
|
||||||
|
"extendResult": null,
|
||||||
|
},
|
||||||
"errors": [{
|
"errors": [{
|
||||||
"message": "my error",
|
"message": "my error",
|
||||||
"locations": [{
|
"locations": [{
|
||||||
|
|
|
@ -376,27 +376,30 @@ pub async fn test_subscription_fieldresult() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
|
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
|
||||||
let mut stream = schema
|
let mut stream = schema.execute_stream("subscription { values }");
|
||||||
.execute_stream("subscription { values }")
|
|
||||||
.map(|resp| resp.into_result())
|
|
||||||
.map_ok(|resp| resp.data);
|
|
||||||
for i in 0i32..5 {
|
for i in 0i32..5 {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value!({ "values": i }),
|
Response::new(value!({ "values": i })),
|
||||||
stream.next().await.unwrap().unwrap()
|
stream.next().await.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stream.next().await,
|
Response {
|
||||||
Some(Err(vec![ServerError {
|
data: value!({ "values": null }),
|
||||||
message: "StreamErr".to_string(),
|
extensions: Default::default(),
|
||||||
locations: vec![Pos {
|
cache_control: Default::default(),
|
||||||
line: 1,
|
errors: vec![ServerError {
|
||||||
column: 16
|
message: "StreamErr".to_string(),
|
||||||
|
locations: vec![Pos {
|
||||||
|
line: 1,
|
||||||
|
column: 16
|
||||||
|
}],
|
||||||
|
path: vec![PathSegment::Field("values".to_owned())],
|
||||||
|
extensions: None,
|
||||||
}],
|
}],
|
||||||
path: vec![PathSegment::Field("values".to_owned())],
|
http_headers: Default::default()
|
||||||
extensions: None,
|
},
|
||||||
}]))
|
stream.next().await.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(stream.next().await.is_none());
|
assert!(stream.next().await.is_none());
|
||||||
|
|
|
@ -262,7 +262,9 @@ pub async fn test_subscription_ws_transport_error() {
|
||||||
"type": "next",
|
"type": "next",
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"payload": {
|
"payload": {
|
||||||
"data": null,
|
"data": {
|
||||||
|
"events": { "value": null }
|
||||||
|
},
|
||||||
"errors": [{
|
"errors": [{
|
||||||
"message": "TestError",
|
"message": "TestError",
|
||||||
"locations": [{"line": 1, "column": 25}],
|
"locations": [{"line": 1, "column": 25}],
|
||||||
|
|
|
@ -258,7 +258,7 @@ pub async fn test_subscription_ws_transport_error() {
|
||||||
"type": "data",
|
"type": "data",
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"payload": {
|
"payload": {
|
||||||
"data": null,
|
"data": { "events": { "value": null } },
|
||||||
"errors": [{
|
"errors": [{
|
||||||
"message": "TestError",
|
"message": "TestError",
|
||||||
"locations": [{"line": 1, "column": 25}],
|
"locations": [{"line": 1, "column": 25}],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user