Skip to content

Commit

Permalink
Allow custom errors to be returned from queries, mutations (#205)
Browse files Browse the repository at this point in the history
* Added trait to convert a custom error type into a FieldError
* Convert the error type of the gql fields if it implements IntoFieldError
* Added test case to check if custom error handling works
* Added to changelog
  • Loading branch information
pepsighan authored and LegNeato committed Jul 13, 2018
1 parent 2e9408e commit f115d0b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 3 deletions.
6 changes: 6 additions & 0 deletions changelog/master.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 17 additions & 1 deletion juniper/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,21 @@ pub type ExecutionResult = Result<Value, FieldError>;
/// The map of variables used for substitution during query execution
pub type Variables = HashMap<String, InputValue>;

/// 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)]
Expand All @@ -208,12 +223,13 @@ where
}
}

impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<T>
impl<'a, T: GraphQLType, C, E: IntoFieldError> IntoResolvable<'a, T, C> for Result<T, E>
where
T::Context: FromContext<C>,
{
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
self.map(|v| Some((FromContext::from(ctx), v)))
.map_err(|e| e.into_field_error())
}
}

Expand Down
43 changes: 42 additions & 1 deletion juniper/src/executor_tests/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Inner> { (0..5).map(|_| Inner).collect() }
Expand All @@ -696,6 +713,7 @@ mod propagates_errors_to_nullable_fields {
field non_nullable_field() -> Inner { Inner }
field nullable_error_field() -> FieldResult<Option<&str>> { 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]
Expand Down Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down

0 comments on commit f115d0b

Please sign in to comment.