Skip to content

Allow custom errors to be returned from queries, mutations #205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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