diff --git a/changelog/master.md b/changelog/master.md index 5dbb81382..fdaac4cdd 100644 --- a/changelog/master.md +++ b/changelog/master.md @@ -28,3 +28,9 @@ `#[graphql(description = "my description")]`. [#194](https://github.com/graphql-rust/juniper/issues/194) + +* Introduced `IntoFieldError` trait to allow custom error handling + i.e. custom result type. The error type must implement this trait resolving + the errors into `FieldError`. + + [#40](https://github.com/graphql-rust/juniper/issues/40) diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index eb879be92..92795fff2 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -193,6 +193,21 @@ pub type ExecutionResult = Result; /// The map of variables used for substitution during query execution pub type Variables = HashMap; +/// Custom error handling trait to enable Error types other than `FieldError` to be specified +/// as return value. +/// +/// Any custom error type should implement this trait to convert it to `FieldError`. +pub trait IntoFieldError { + #[doc(hidden)] + fn into_field_error(self) -> FieldError; +} + +impl IntoFieldError for FieldError { + fn into_field_error(self) -> FieldError { + self + } +} + #[doc(hidden)] pub trait IntoResolvable<'a, T: GraphQLType, C>: Sized { #[doc(hidden)] @@ -208,12 +223,13 @@ where } } -impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult +impl<'a, T: GraphQLType, C, E: IntoFieldError> IntoResolvable<'a, T, C> for Result where T::Context: FromContext, { fn into(self, ctx: &'a C) -> FieldResult> { self.map(|v| Some((FromContext::from(ctx), v))) + .map_err(|e| e.into_field_error()) } } diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index 7da8fb0fa..95e03fe5a 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -676,7 +676,7 @@ mod dynamic_context_switching { } mod propagates_errors_to_nullable_fields { - use executor::{ExecutionError, FieldError, FieldResult}; + use executor::{ExecutionError, FieldError, FieldResult, IntoFieldError}; use parser::SourcePosition; use schema::model::RootNode; use types::scalars::EmptyMutation; @@ -685,6 +685,23 @@ mod propagates_errors_to_nullable_fields { struct Schema; struct Inner; + enum CustomError { + NotFound + } + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + match self { + CustomError::NotFound => FieldError::new( + "Not Found", + graphql_value!({ + "type": "NOT_FOUND" + }) + ) + } + } + } + graphql_object!(Schema: () |&self| { field inner() -> Inner { Inner } field inners() -> Vec { (0..5).map(|_| Inner).collect() } @@ -696,6 +713,7 @@ mod propagates_errors_to_nullable_fields { field non_nullable_field() -> Inner { Inner } field nullable_error_field() -> FieldResult> { Err("Error for nullableErrorField")? } field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? } + field custom_error_field() -> Result<&str, CustomError> { Err(CustomError::NotFound) } }); #[test] @@ -747,6 +765,29 @@ mod propagates_errors_to_nullable_fields { ); } + #[test] + fn custom_error_first_level() { + let schema = RootNode::new(Schema, EmptyMutation::<()>::new()); + let doc = r"{ inner { customErrorField } }"; + + let vars = vec![].into_iter().collect(); + + let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed"); + + println!("Result: {:?}", result); + + assert_eq!(result, graphql_value!(None)); + + assert_eq!( + errs, + vec![ExecutionError::new( + SourcePosition::new(10, 0, 10), + &["inner", "customErrorField"], + FieldError::new("Not Found", graphql_value!({ "type": "NOT_FOUND" })), + )] + ); + } + #[test] fn nullable_nested_level() { let schema = RootNode::new(Schema, EmptyMutation::<()>::new()); diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 356f02f33..f3a75fbbd 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -152,7 +152,7 @@ use validation::{validate_input_values, visit_all_rules, ValidatorContext}; pub use ast::{FromInputValue, InputValue, Selection, ToInputValue, Type}; pub use executor::{Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, - FromContext, IntoResolvable, Registry, Variables}; + FromContext, IntoResolvable, Registry, Variables, IntoFieldError}; pub use executor::{Applies, LookAheadArgument, LookAheadSelection, LookAheadValue, LookAheadMethods}; pub use schema::model::RootNode; pub use types::base::{Arguments, GraphQLType, TypeKind};