diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 06b4a9c7..8fca9e04 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -311,7 +311,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().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| { quote! { #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 let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); 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)); } }); diff --git a/derive/src/enum.rs b/derive/src/enum.rs index d3bfc185..84065d12 100644 --- a/derive/src/enum.rs +++ b/derive/src/enum.rs @@ -162,8 +162,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult { #[#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::ServerResult<#crate_name::Value> { - ::std::result::Result::Ok(#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::Value { + #crate_name::resolver_utils::enum_value(*self) } } diff --git a/derive/src/interface.rs b/derive/src/interface.rs index def538a1..61c8c683 100644 --- a/derive/src/interface.rs +++ b/derive/src/interface.rs @@ -289,14 +289,14 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult::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! { if ctx.item.node.name.node == #name { #(#get_params)* 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, _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 } } diff --git a/derive/src/merged_object.rs b/derive/src/merged_object.rs index 092fabfb..2d137297 100644 --- a/derive/src/merged_object.rs +++ b/derive/src/merged_object.rs @@ -104,7 +104,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult, _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 } } diff --git a/derive/src/newtype.rs b/derive/src/newtype.rs index cb1f2250..06dfa43a 100644 --- a/derive/src/newtype.rs +++ b/derive/src/newtype.rs @@ -103,8 +103,8 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult { &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::ScalarType::to_value(self)) + ) -> #crate_name::Value { + #crate_name::ScalarType::to_value(self) } } }; diff --git a/derive/src/object.rs b/derive/src/object.rs index 9fe25fe8..d10de221 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -178,7 +178,7 @@ pub fn generate( // requires requires_getter.push(quote! { 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); } @@ -209,7 +209,11 @@ pub fn generate( syn::parse2::(quote! { -> #crate_name::Result<#inner_ty> }) .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(( args.len(), @@ -219,7 +223,7 @@ pub fn generate( if let (#(#key_pat),*) = (#(#key_getter),*) { #(#requires_getter)* 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 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| { quote! { - #guard.check(ctx).await - .map_err(|err| err.into_server_error().at(ctx.item.pos))?; + #guard.check(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?; } }); @@ -536,7 +539,7 @@ pub fn generate( #guard let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); 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 } else { return ::std::result::Result::Err( - #crate_name::ServerError::new(r#""__typename" must be an existing string."#) - .at(ctx.item.pos) + #crate_name::ServerError::new(r#""__typename" must be an existing string."#, ::std::option::Option::Some(ctx.item.pos)) ); }; #(#find_entities_iter)* @@ -631,7 +633,7 @@ 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::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 } } diff --git a/derive/src/scalar.rs b/derive/src/scalar.rs index 561ee384..f1f06a3c 100644 --- a/derive/src/scalar.rs +++ b/derive/src/scalar.rs @@ -67,8 +67,8 @@ pub fn generate( &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::ScalarType::to_value(self)) + ) -> #crate_name::Value { + #crate_name::ScalarType::to_value(self) } } }; diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index a4021e31..a5dbd9e9 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -100,7 +100,9 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult generate_guards(&crate_name, &meta)?, 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 { quote! { @@ -123,9 +125,9 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult, _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 } } @@ -275,7 +277,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult, _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 } } diff --git a/derive/src/subscription.rs b/derive/src/subscription.rs index f5e3cc16..10b2a2f9 100644 --- a/derive/src/subscription.rs +++ b/derive/src/subscription.rs @@ -318,9 +318,8 @@ pub fn generate( self.#ident(ctx, #(#use_params),*) .await .map_err(|err| { - ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error() - .at(ctx.item.pos) - .path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))) + ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos) + .with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))]) })? }; @@ -330,12 +329,10 @@ pub fn generate( }; let guard = guard.map(|guard| quote! { #guard.check(ctx).await.map_err(|err| { - err.into_server_error() - .at(ctx.item.pos) - .path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))) + err.into_server_error(ctx.item.pos) + .with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))]) })?; }); - let stream_fn = quote! { 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)); @@ -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(), }; let resolve_fut = async { - #crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field) - .await - .map(::std::option::Option::Some) + ::std::result::Result::Ok(::std::option::Option::Some(#crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field).await)) }; #crate_name::futures_util::pin_mut!(resolve_fut); - query_env.extensions.resolve(ri, &mut resolve_fut).await - .map(|value| { - let mut map = ::std::collections::BTreeMap::new(); - 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.path(#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))) - ]) - }) + let mut resp = query_env.extensions.resolve(ri, &mut resolve_fut).await.map(|value| { + let mut map = ::std::collections::BTreeMap::new(); + 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()); + resp }; #crate_name::futures_util::pin_mut!(execute_fut); ::std::result::Result::Ok(query_env.extensions.execute(&mut execute_fut).await) @@ -474,5 +470,6 @@ pub fn generate( } } }; + Ok(expanded.into()) } diff --git a/derive/src/union.rs b/derive/src/union.rs index e6c1efb5..80ca489c 100644 --- a/derive/src/union.rs +++ b/derive/src/union.rs @@ -200,7 +200,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { #[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::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 } } diff --git a/integrations/tide/tests/graphql.rs b/integrations/tide/tests/graphql.rs index f685d173..2b03d926 100644 --- a/integrations/tide/tests/graphql.rs +++ b/integrations/tide/tests/graphql.rs @@ -2,16 +2,15 @@ mod test_utils; use std::io::Read; +use async_graphql::*; use reqwest::{header, StatusCode}; use serde_json::json; -use async_graphql::*; - type Result = std::result::Result>; #[async_std::test] 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 { struct QueryRoot; @@ -36,7 +35,7 @@ async fn quickstart() -> Result<()> { let client = test_utils::client(); let resp = client - .post(listen_addr) + .post(&format!("http://{}", listen_addr)) .json(&json!({"query":"{ add(a: 10, b: 20) }"})) .send() .await?; @@ -48,7 +47,7 @@ async fn quickstart() -> Result<()> { assert_eq!(string, json!({"data": {"add": 30}}).to_string()); let resp = client - .get(listen_addr) + .get(&format!("http://{}", listen_addr)) .query(&[("query", "{ add(a: 10, b: 20) }")]) .send() .await?; @@ -64,7 +63,7 @@ async fn quickstart() -> Result<()> { #[async_std::test] 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 { struct Hello(String); @@ -104,7 +103,7 @@ async fn hello() -> Result<()> { let client = test_utils::client(); let resp = client - .post(listen_addr) + .post(&format!("http://{}", listen_addr)) .json(&json!({"query":"{ hello }"})) .header("Name", "Foo") .send() @@ -117,7 +116,7 @@ async fn hello() -> Result<()> { assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string()); let resp = client - .post(listen_addr) + .post(&format!("http://{}", listen_addr)) .json(&json!({"query":"{ hello }"})) .header(header::CONTENT_TYPE, "application/json") .send() @@ -137,7 +136,7 @@ async fn hello() -> Result<()> { #[async_std::test] 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 { struct QueryRoot; @@ -198,7 +197,11 @@ async fn upload() -> Result<()> { .text("map", r#"{ "0": ["variables.file"] }"#) .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); let string = resp.text().await?; diff --git a/integrations/tide/tests/test_utils.rs b/integrations/tide/tests/test_utils.rs index 0786a68b..5d0e2a2b 100644 --- a/integrations/tide/tests/test_utils.rs +++ b/integrations/tide/tests/test_utils.rs @@ -1,19 +1,6 @@ use reqwest::Client; 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 { Client::builder().no_proxy().build().unwrap() } diff --git a/src/base.rs b/src/base.rs index c3d3b938..9b0969cc 100644 --- a/src/base.rs +++ b/src/base.rs @@ -57,11 +57,7 @@ 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, - ) -> ServerResult; + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value; } impl Type for &T { @@ -77,11 +73,7 @@ impl Type for &T { #[async_trait::async_trait] impl OutputType for &T { #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { T::resolve(*self, ctx, field).await } } @@ -102,14 +94,13 @@ impl + Send + Sync + Clone> Type for Result { #[async_trait::async_trait] impl + Send + Sync + Clone> OutputType for Result { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { match self { - Ok(value) => Ok(value.resolve(ctx, field).await?), - Err(err) => Err(err.clone().into().into_server_error().at(field.pos)), + Ok(value) => value.resolve(ctx, field).await, + Err(err) => { + ctx.add_error(err.clone().into().into_server_error(field.pos)); + Value::Null + } } } } @@ -142,11 +133,7 @@ impl Type for Box { #[async_trait::async_trait] impl OutputType for Box { #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { T::resolve(&**self, ctx, field).await } } @@ -177,11 +164,7 @@ impl Type for Arc { #[async_trait::async_trait] impl OutputType for Arc { #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { T::resolve(&**self, ctx, field).await } } diff --git a/src/context.rs b/src/context.rs index d2686ffb..61c590ac 100644 --- a/src/context.rs +++ b/src/context.rs @@ -18,8 +18,8 @@ use crate::parser::types::{ }; use crate::schema::SchemaEnv; use crate::{ - Error, InputType, Lookahead, Name, Pos, Positioned, Result, ServerError, ServerResult, - UploadValue, Value, + Error, InputType, Lookahead, Name, PathSegment, Pos, Positioned, Result, ServerError, + ServerResult, UploadValue, Value, }; /// Schema/Context data. @@ -214,6 +214,7 @@ pub struct QueryEnvInner { pub ctx_data: Arc, pub http_headers: Mutex>, pub disable_introspection: bool, + pub errors: Mutex>, } #[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`. /// /// 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()) }) .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) -> ServerResult { @@ -486,7 +516,7 @@ impl<'a, T> ContextBase<'a, T> { let condition_input = directive .node .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(); let pos = condition_input.pos; @@ -494,7 +524,7 @@ impl<'a, T> ContextBase<'a, T> { if include != ::parse(Some(condition_input)) - .map_err(|e| e.into_server_error().at(pos))? + .map_err(|e| e.into_server_error(pos))? { return Ok(true); } @@ -536,7 +566,7 @@ impl<'a> ContextBase<'a, &'a Positioned> { Some(value) => (value.pos, Some(self.resolve_input_value(value)?)), 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. diff --git a/src/error.rs b/src/error.rs index 3ac53830..d082dfaa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -41,25 +41,18 @@ fn error_extensions_is_empty(values: &Option) -> bool { impl ServerError { /// Create a new server error with the message. - pub fn new(message: impl Into) -> Self { + pub fn new(message: impl Into, pos: Option) -> Self { Self { message: message.into(), - locations: Vec::new(), + locations: pos.map(|pos| vec![pos]).unwrap_or_default(), path: Vec::new(), extensions: None, } } - /// Add a position to the error. - pub fn at(mut self, at: Pos) -> Self { - self.locations.push(at); - self - } - - /// Prepend a path to the error. - pub fn path(mut self, path: PathSegment) -> Self { - self.path.insert(0, path); - self + #[doc(hidden)] + pub fn with_path(self, path: Vec) -> Self { + Self { path, ..self } } } @@ -75,12 +68,6 @@ impl From for Vec { } } -impl From for ServerError { - fn from(e: Error) -> Self { - e.into_server_error() - } -} - impl From for ServerError { fn from(e: parser::Error) -> Self { Self { @@ -158,8 +145,8 @@ impl InputValueError { } /// Convert the error into a server error. - pub fn into_server_error(self) -> ServerError { - ServerError::new(self.message) + pub fn into_server_error(self, pos: Pos) -> ServerError { + ServerError::new(self.message, Some(pos)) } } @@ -193,10 +180,10 @@ impl Error { /// Convert the error to a server error. #[must_use] - pub fn into_server_error(self) -> ServerError { + pub fn into_server_error(self, pos: Pos) -> ServerError { ServerError { message: self.message, - locations: Vec::new(), + locations: vec![pos], path: Vec::new(), extensions: self.extensions, } diff --git a/src/extensions/apollo_persisted_queries.rs b/src/extensions/apollo_persisted_queries.rs index d892e70f..9946c827 100644 --- a/src/extensions/apollo_persisted_queries.rs +++ b/src/extensions/apollo_persisted_queries.rs @@ -85,11 +85,11 @@ impl Extension for ApolloPersistedQueriesExtension { ) -> ServerResult { let res = if let Some(value) = request.extensions.remove("persistedQuery") { 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 { 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 Extension for ApolloPersistedQueriesExtension { if let Some(query) = self.storage.get(persisted_query.sha256_hash).await { Ok(Request { query, ..request }) } else { - Err(ServerError::new("PersistedQueryNotFound".to_string())) + Err(ServerError::new("PersistedQueryNotFound", None)) } } else { let sha256_hash = format!("{:x}", Sha256::digest(request.query.as_bytes())); 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 { self.storage.set(sha256_hash, request.query.clone()).await; Ok(request) @@ -179,7 +179,7 @@ mod tests { assert_eq!( schema.execute(request).await.into_result().unwrap_err(), - vec![ServerError::new("PersistedQueryNotFound")] + vec![ServerError::new("PersistedQueryNotFound", None)] ); } } diff --git a/src/resolver_utils/container.rs b/src/resolver_utils/container.rs index 46cca142..e81aac5e 100644 --- a/src/resolver_utils/container.rs +++ b/src/resolver_utils/container.rs @@ -5,9 +5,7 @@ use std::pin::Pin; use crate::extensions::ResolveInfo; use crate::parser::types::Selection; use crate::registry::MetaType; -use crate::{ - Context, ContextSelectionSet, Name, OutputType, PathSegment, ServerError, ServerResult, Value, -}; +use crate::{Context, ContextSelectionSet, Name, OutputType, ServerError, ServerResult, Value}; /// Represents a GraphQL container object. /// @@ -64,7 +62,7 @@ impl ContainerType for &T { pub async fn resolve_container<'a, T: ContainerType + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, -) -> ServerResult { +) -> Value { 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>( ctx: &ContextSelectionSet<'a>, root: &'a T, -) -> ServerResult { +) -> Value { resolve_container_inner(ctx, root, false).await } @@ -106,16 +104,20 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, parallel: bool, -) -> ServerResult { +) -> Value { 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 { - futures_util::future::try_join_all(fields.0).await? + futures_util::future::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 }; @@ -124,10 +126,10 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( for (name, value) in res { insert_value(&mut map, name, value); } - Ok(Value::Object(map)) + Value::Object(map) } -type BoxFieldFuture<'a> = Pin> + 'a + Send>>; +type BoxFieldFuture<'a> = Pin + 'a + Send>>; /// A set of fields on an container that are being selected. pub struct Fields<'a>(Vec>); @@ -152,9 +154,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 { - Ok((field_name, Value::String(typename))) - })); + self.0.push(Box::pin( + async move { (field_name, Value::String(typename)) }, + )); continue; } @@ -177,9 +179,10 @@ impl<'a> Fields<'a> { if extensions.is_empty() { 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.path(PathSegment::Field(field_name.to_string()))) + ctx_field.add_error(e); + (field_name, Value::Null) } } } else { @@ -199,23 +202,26 @@ impl<'a> Fields<'a> { { Some(ty) => &ty, None => { - return Err(ServerError::new(format!( - r#"Cannot query field "{}" on type "{}"."#, - field_name, type_name - )) - .at(ctx_field.item.pos) - .path(PathSegment::Field(field_name.to_string()))); + ctx_field.add_error(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 = async { root.resolve_field(&ctx_field).await }; + 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) => Ok((field_name, value.unwrap_or_default())), - Err(e) => { - Err(e.path(PathSegment::Field(field_name.to_string()))) + Ok(value) => (field_name, value.unwrap_or_default()), + Err(err) => { + ctx.add_error(err); + (field_name, Value::Null) } } } @@ -231,11 +237,13 @@ impl<'a> Fields<'a> { let fragment = match fragment { Some(fragment) => fragment, None => { - return Err(ServerError::new(format!( - r#"Unknown fragment "{}"."#, - spread.node.fragment_name.node - )) - .at(spread.pos)); + return Err(ServerError::new( + format!( + r#"Unknown fragment "{}"."#, + spread.node.fragment_name.node + ), + Some(spread.pos), + )); } }; ( diff --git a/src/resolver_utils/list.rs b/src/resolver_utils/list.rs index cf2cccba..f80fb813 100644 --- a/src/resolver_utils/list.rs +++ b/src/resolver_utils/list.rs @@ -1,6 +1,6 @@ use crate::extensions::ResolveInfo; 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. pub async fn resolve_list<'a, T: OutputType + 'a>( @@ -8,7 +8,7 @@ pub async fn resolve_list<'a, T: OutputType + 'a>( field: &Positioned, iter: impl IntoIterator, len: Option, -) -> ServerResult { +) -> Value { let extensions = &ctx.query_env.extensions; if !extensions.is_empty() { 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::::type_name(), return_type: &T::qualified_type_name(), }; - let resolve_fut = async { - OutputType::resolve(&item, &ctx_idx, field) - .await - .map(Option::Some) - .map_err(|e| e.path(PathSegment::Index(idx))) - }; + let resolve_fut = + async { Ok(Some(OutputType::resolve(&item, &ctx_idx, field).await)) }; futures_util::pin_mut!(resolve_fut); extensions .resolve(resolve_info, &mut resolve_fut) .await - .map(|value| value.expect("You definitely encountered a bug!")) + .unwrap() + .expect("You definitely encountered a bug!") } }); } - Ok(Value::List( - futures_util::future::try_join_all(futures).await?, - )) + Value::List(futures_util::future::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 - .map_err(|e| e.path(PathSegment::Index(idx))) - }); + futures.push(async move { OutputType::resolve(&item, &ctx_idx, field).await }); } - Ok(Value::List( - futures_util::future::try_join_all(futures).await?, - )) + Value::List(futures_util::future::join_all(futures).await) } } diff --git a/src/resolver_utils/scalar.rs b/src/resolver_utils/scalar.rs index 2a788345..9e75cc5f 100644 --- a/src/resolver_utils/scalar.rs +++ b/src/resolver_utils/scalar.rs @@ -154,8 +154,8 @@ macro_rules! scalar_internal { &self, _: &$crate::ContextSelectionSet<'_>, _field: &$crate::Positioned<$crate::parser::types::Field>, - ) -> $crate::ServerResult<$crate::Value> { - ::std::result::Result::Ok($crate::ScalarType::to_value(self)) + ) -> $crate::Value { + $crate::ScalarType::to_value(self) } } }; diff --git a/src/response.rs b/src/response.rs index acf23180..1a3fea0a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{CacheControl, Result, ServerError, Value}; /// Query response -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Response { /// Data of query result #[serde(default)] diff --git a/src/schema.rs b/src/schema.rs index 7cde9e02..1673cfa2 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -404,13 +404,13 @@ where // check limit if let Some(limit_complexity) = self.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 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(|| { - ServerError::new(format!(r#"Unknown operation named "{}""#, operation_name)) + ServerError::new( + format!(r#"Unknown operation named "{}""#, operation_name), + None, + ) }) } else { match document.operations { @@ -430,9 +433,10 @@ where DocumentOperations::Multiple(map) if map.len() == 1 => { Ok(map.into_iter().next().unwrap().1) } - DocumentOperations::Multiple(_) => { - Err(ServerError::new("Operation name required in request.")) - } + DocumentOperations::Multiple(_) => Err(ServerError::new( + "Operation name required in request.", + None, + )), } }; let operation = match operation { @@ -450,6 +454,7 @@ where ctx_data: query_data, http_headers: Default::default(), disable_introspection: request.disable_introspection, + errors: Default::default(), }; Ok((QueryEnv::new(env), validation_result.cache_control)) } @@ -463,23 +468,21 @@ where 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::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, )]); } }; - match res { - Ok(data) => { - let resp = Response::new(data); - resp.http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap())) - } - Err(err) => Response::from_errors(vec![err]), - } + 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()); + resp } /// Execute a GraphQL query. diff --git a/src/subscription.rs b/src/subscription.rs index a25d0ad6..0ac9418e 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -43,9 +43,8 @@ pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + 'static>( yield resp; } } else { - let err = ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name())) - .at(ctx.item.pos) - .path(PathSegment::Field(field_name.to_string())); + let err = ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()), Some(ctx.item.pos)) + .with_path(vec![PathSegment::Field(field_name.to_string())]); yield Response::from_errors(vec![err]); } } diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs index 9080004a..2c7031ff 100644 --- a/src/types/connection/connection_type.rs +++ b/src/types/connection/connection_type.rs @@ -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 OutputType::resolve(&page_info, &ctx_obj, ctx.item) - .await - .map(Some); + return Ok(Some( + OutputType::resolve(&page_info, &ctx_obj, ctx.item).await, + )); } else if ctx.item.node.name.node == "edges" { let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return OutputType::resolve(&self.edges, &ctx_obj, ctx.item) - .await - .map(Some); + return Ok(Some( + OutputType::resolve(&self.edges, &ctx_obj, ctx.item).await, + )); } self.additional_fields.resolve_field(ctx).await @@ -234,11 +234,7 @@ where EC: ObjectType, EE: ObjectType, { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { resolve_container(ctx, self).await } } diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs index f6c893bb..53ba593a 100644 --- a/src/types/connection/edge.rs +++ b/src/types/connection/edge.rs @@ -122,9 +122,9 @@ where async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { if ctx.item.node.name.node == "node" { let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return OutputType::resolve(&self.node, &ctx_obj, ctx.item) - .await - .map(Some); + return Ok(Some( + OutputType::resolve(&self.node, &ctx_obj, ctx.item).await, + )); } else if ctx.item.node.name.node == "cursor" { return Ok(Some(Value::String(self.cursor.encode_cursor()))); } @@ -140,11 +140,7 @@ where T: OutputType, E: ObjectType, { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { resolve_container(ctx, self).await } } diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs index 1bf66bf1..a701b139 100644 --- a/src/types/empty_mutation.rs +++ b/src/types/empty_mutation.rs @@ -62,12 +62,12 @@ impl ContainerType for EmptyMutation { #[async_trait::async_trait] impl OutputType for EmptyMutation { - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - Err(ServerError::new("Schema is not configured for mutations.").at(field.pos)) + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { + ctx.add_error(ServerError::new( + "Schema is not configured for mutations.", + None, + )); + Value::Null } } diff --git a/src/types/empty_subscription.rs b/src/types/empty_subscription.rs index aa4f5a3c..0c98e473 100644 --- a/src/types/empty_subscription.rs +++ b/src/types/empty_subscription.rs @@ -36,13 +36,13 @@ impl SubscriptionType for EmptySubscription { fn create_field_stream<'a>( &'a self, - ctx: &'a Context<'_>, + _ctx: &'a Context<'_>, ) -> Option + Send + 'a>>> where Self: Send + Sync + 'static + Sized, { 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]) }))) } diff --git a/src/types/external/cow.rs b/src/types/external/cow.rs index e6dc1189..f83ae3cb 100644 --- a/src/types/external/cow.rs +++ b/src/types/external/cow.rs @@ -1,6 +1,6 @@ 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; impl<'a, T> Type for Cow<'a, T> @@ -22,11 +22,7 @@ where T: OutputType + ToOwned + ?Sized, ::Owned: Send + Sync, { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { self.as_ref().resolve(ctx, field).await } } diff --git a/src/types/external/list/btree_set.rs b/src/types/external/list/btree_set.rs index 76279f7e..d715c7db 100644 --- a/src/types/external/list/btree_set.rs +++ b/src/types/external/list/btree_set.rs @@ -5,7 +5,7 @@ use crate::parser::types::Field; use crate::resolver_utils::resolve_list; use crate::{ registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, - Positioned, ServerResult, Type, Value, + Positioned, Type, Value, }; impl Type for BTreeSet { @@ -46,11 +46,7 @@ impl InputType for BTreeSet { #[async_trait::async_trait] impl OutputType for BTreeSet { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self, Some(self.len())).await } } diff --git a/src/types/external/list/hash_set.rs b/src/types/external/list/hash_set.rs index 798bcd71..da2a8808 100644 --- a/src/types/external/list/hash_set.rs +++ b/src/types/external/list/hash_set.rs @@ -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, ServerResult, Type, Value, + Positioned, Result, Type, Value, }; impl Type for HashSet { @@ -48,11 +48,7 @@ impl InputType for HashSet { #[async_trait::async_trait] impl OutputType for HashSet { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self, Some(self.len())).await } } diff --git a/src/types/external/list/linked_list.rs b/src/types/external/list/linked_list.rs index 768f5d93..858af663 100644 --- a/src/types/external/list/linked_list.rs +++ b/src/types/external/list/linked_list.rs @@ -5,7 +5,7 @@ use crate::parser::types::Field; use crate::resolver_utils::resolve_list; use crate::{ registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, - Positioned, ServerResult, Type, Value, + Positioned, Type, Value, }; impl Type for LinkedList { @@ -47,11 +47,7 @@ impl InputType for LinkedList { #[async_trait::async_trait] impl OutputType for LinkedList { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self, Some(self.len())).await } } diff --git a/src/types/external/list/slice.rs b/src/types/external/list/slice.rs index ec5e5137..cc163e24 100644 --- a/src/types/external/list/slice.rs +++ b/src/types/external/list/slice.rs @@ -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, ServerResult, Type, Value}; +use crate::{registry, ContextSelectionSet, OutputType, Positioned, Type, Value}; impl<'a, T: Type + 'a> Type for &'a [T] { fn type_name() -> Cow<'static, str> { @@ -21,11 +21,7 @@ impl<'a, T: Type + 'a> Type for &'a [T] { #[async_trait::async_trait] impl OutputType for &[T] { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self.iter(), Some(self.len())).await } } diff --git a/src/types/external/list/vec.rs b/src/types/external/list/vec.rs index 31bb73e1..61c1229f 100644 --- a/src/types/external/list/vec.rs +++ b/src/types/external/list/vec.rs @@ -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, ServerResult, Type, Value, + Positioned, Result, Type, Value, }; impl Type for Vec { @@ -43,11 +43,7 @@ impl InputType for Vec { #[async_trait::async_trait] impl OutputType for Vec { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self, Some(self.len())).await } } diff --git a/src/types/external/list/vec_deque.rs b/src/types/external/list/vec_deque.rs index e3fd760b..eb42013a 100644 --- a/src/types/external/list/vec_deque.rs +++ b/src/types/external/list/vec_deque.rs @@ -5,7 +5,7 @@ use crate::parser::types::Field; use crate::resolver_utils::resolve_list; use crate::{ registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, - Positioned, ServerResult, Type, Value, + Positioned, Type, Value, }; impl Type for VecDeque { @@ -47,11 +47,7 @@ impl InputType for VecDeque { #[async_trait::async_trait] impl OutputType for VecDeque { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { resolve_list(ctx, field, self, Some(self.len())).await } } diff --git a/src/types/external/optional.rs b/src/types/external/optional.rs index 6d7fd1d6..bcaa4091 100644 --- a/src/types/external/optional.rs +++ b/src/types/external/optional.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::parser::types::Field; use crate::{ registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, - Positioned, ServerResult, Type, Value, + Positioned, Type, Value, }; impl Type for Option { @@ -41,15 +41,11 @@ impl InputType for Option { #[async_trait::async_trait] impl OutputType for Option { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, field: &Positioned) -> Value { if let Some(inner) = self { OutputType::resolve(inner, ctx, field).await } else { - Ok(Value::Null) + Value::Null } } } diff --git a/src/types/external/string.rs b/src/types/external/string.rs index 3852c51d..53b5c6c4 100644 --- a/src/types/external/string.rs +++ b/src/types/external/string.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::parser::types::Field; use crate::{ 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. @@ -37,11 +37,7 @@ impl Type for str { #[async_trait::async_trait] impl OutputType for str { - async fn resolve( - &self, - _: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Ok(Value::String(self.to_string())) + async fn resolve(&self, _: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { + Value::String(self.to_string()) } } diff --git a/src/types/json.rs b/src/types/json.rs index 065397f9..b3edee15 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -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, ServerResult, Type, Value, + Positioned, Scalar, ScalarType, Type, Value, }; /// A scalar that can represent any JSON value. @@ -91,12 +91,8 @@ impl Type for OutputJson { #[async_trait::async_trait] impl OutputType for OutputJson { - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Ok(to_value(&self.0).ok().unwrap_or_default()) + async fn resolve(&self, _ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { + to_value(&self.0).ok().unwrap_or_default() } } diff --git a/src/types/merged_object.rs b/src/types/merged_object.rs index 5ea99e51..981d2414 100644 --- a/src/types/merged_object.rs +++ b/src/types/merged_object.rs @@ -90,11 +90,7 @@ where A: ObjectType, B: ObjectType, { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { resolve_container(ctx, self).await } } diff --git a/src/types/query_root.rs b/src/types/query_root.rs index 6640e8b2..de1a3bae 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -95,30 +95,32 @@ impl ContainerType for QueryRoot { 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 OutputType::resolve( - &__Schema { - registry: &ctx.schema_env.registry, - }, - &ctx_obj, - ctx.item, - ) - .await - .map(Some); + return Ok(Some( + OutputType::resolve( + &__Schema { + registry: &ctx.schema_env.registry, + }, + &ctx_obj, + ctx.item, + ) + .await, + )); } 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 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); + 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, + )); } } @@ -127,25 +129,25 @@ impl ContainerType for QueryRoot { let representations: Vec = ctx.param_value("representations", None)?; let res = futures_util::future::try_join_all(representations.iter().map( |item| async move { - self.inner - .find_entity(ctx, &item.0) - .await? - .ok_or_else(|| ServerError::new("Entity not found.").at(ctx.item.pos)) + self.inner.find_entity(ctx, &item.0).await?.ok_or_else(|| { + ServerError::new("Entity not found.", Some(ctx.item.pos)) + }) }, )) .await?; 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 OutputType::resolve( - &Service { - sdl: Some(ctx.schema_env.registry.export_sdl(true)), - }, - &ctx_obj, - ctx.item, - ) - .await - .map(Some); + return Ok(Some( + OutputType::resolve( + &Service { + sdl: Some(ctx.schema_env.registry.export_sdl(true)), + }, + &ctx_obj, + ctx.item, + ) + .await, + )); } } @@ -155,11 +157,7 @@ impl ContainerType for QueryRoot { #[async_trait::async_trait] impl OutputType for QueryRoot { - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { + async fn resolve(&self, ctx: &ContextSelectionSet<'_>, _field: &Positioned) -> Value { resolve_container(ctx, self).await } } diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index b6b9f79d..e270c7df 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -126,8 +126,10 @@ impl<'a> VisitorContext<'a> { }) .cloned() .ok_or_else(|| { - ServerError::new(format!("Variable {} is not defined.", name)) - .at(pos) + ServerError::new( + format!("Variable {} is not defined.", name), + Some(pos), + ) }) })?), ) @@ -135,7 +137,7 @@ impl<'a> VisitorContext<'a> { 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)) } } diff --git a/tests/error_ext.rs b/tests/error_ext.rs index b26c4fc6..dee29eef 100644 --- a/tests/error_ext.rs +++ b/tests/error_ext.rs @@ -30,7 +30,9 @@ pub async fn test_error_extensions() { assert_eq!( serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(), serde_json::json!({ - "data": null, + "data": { + "extendErr": null, + }, "errors": [{ "message": "my error", "locations": [{ @@ -49,7 +51,9 @@ pub async fn test_error_extensions() { assert_eq!( serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(), serde_json::json!({ - "data": null, + "data": { + "extendResult": null, + }, "errors": [{ "message": "my error", "locations": [{ diff --git a/tests/subscription.rs b/tests/subscription.rs index 1477c9a1..675627a0 100644 --- a/tests/subscription.rs +++ b/tests/subscription.rs @@ -376,27 +376,30 @@ pub async fn test_subscription_fieldresult() { } let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); - let mut stream = schema - .execute_stream("subscription { values }") - .map(|resp| resp.into_result()) - .map_ok(|resp| resp.data); + let mut stream = schema.execute_stream("subscription { values }"); for i in 0i32..5 { assert_eq!( - value!({ "values": i }), - stream.next().await.unwrap().unwrap() + Response::new(value!({ "values": i })), + stream.next().await.unwrap() ); } assert_eq!( - stream.next().await, - Some(Err(vec![ServerError { - message: "StreamErr".to_string(), - locations: vec![Pos { - line: 1, - column: 16 + Response { + data: value!({ "values": null }), + extensions: Default::default(), + cache_control: Default::default(), + errors: vec![ServerError { + 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())], - extensions: None, - }])) + http_headers: Default::default() + }, + stream.next().await.unwrap(), ); assert!(stream.next().await.is_none()); diff --git a/tests/subscription_websocket_graphql_ws.rs b/tests/subscription_websocket_graphql_ws.rs index 60e620b2..110b1fe1 100644 --- a/tests/subscription_websocket_graphql_ws.rs +++ b/tests/subscription_websocket_graphql_ws.rs @@ -262,7 +262,9 @@ pub async fn test_subscription_ws_transport_error() { "type": "next", "id": "1", "payload": { - "data": null, + "data": { + "events": { "value": null } + }, "errors": [{ "message": "TestError", "locations": [{"line": 1, "column": 25}], diff --git a/tests/subscription_websocket_subscriptions_transport_ws.rs b/tests/subscription_websocket_subscriptions_transport_ws.rs index 25a3258f..edc99188 100644 --- a/tests/subscription_websocket_subscriptions_transport_ws.rs +++ b/tests/subscription_websocket_subscriptions_transport_ws.rs @@ -258,7 +258,7 @@ pub async fn test_subscription_ws_transport_error() { "type": "data", "id": "1", "payload": { - "data": null, + "data": { "events": { "value": null } }, "errors": [{ "message": "TestError", "locations": [{"line": 1, "column": 25}],