Skip to content

Commit f115d0b

Browse files
pepsighanLegNeato
authored andcommitted
Allow custom errors to be returned from queries, mutations (#205)
* 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
1 parent 2e9408e commit f115d0b

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

changelog/master.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@
2828
`#[graphql(description = "my description")]`.
2929

3030
[#194](https://github.com/graphql-rust/juniper/issues/194)
31+
32+
* Introduced `IntoFieldError` trait to allow custom error handling
33+
i.e. custom result type. The error type must implement this trait resolving
34+
the errors into `FieldError`.
35+
36+
[#40](https://github.com/graphql-rust/juniper/issues/40)

juniper/src/executor/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,21 @@ pub type ExecutionResult = Result<Value, FieldError>;
193193
/// The map of variables used for substitution during query execution
194194
pub type Variables = HashMap<String, InputValue>;
195195

196+
/// Custom error handling trait to enable Error types other than `FieldError` to be specified
197+
/// as return value.
198+
///
199+
/// Any custom error type should implement this trait to convert it to `FieldError`.
200+
pub trait IntoFieldError {
201+
#[doc(hidden)]
202+
fn into_field_error(self) -> FieldError;
203+
}
204+
205+
impl IntoFieldError for FieldError {
206+
fn into_field_error(self) -> FieldError {
207+
self
208+
}
209+
}
210+
196211
#[doc(hidden)]
197212
pub trait IntoResolvable<'a, T: GraphQLType, C>: Sized {
198213
#[doc(hidden)]
@@ -208,12 +223,13 @@ where
208223
}
209224
}
210225

211-
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<T>
226+
impl<'a, T: GraphQLType, C, E: IntoFieldError> IntoResolvable<'a, T, C> for Result<T, E>
212227
where
213228
T::Context: FromContext<C>,
214229
{
215230
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
216231
self.map(|v| Some((FromContext::from(ctx), v)))
232+
.map_err(|e| e.into_field_error())
217233
}
218234
}
219235

juniper/src/executor_tests/executor.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ mod dynamic_context_switching {
676676
}
677677

678678
mod propagates_errors_to_nullable_fields {
679-
use executor::{ExecutionError, FieldError, FieldResult};
679+
use executor::{ExecutionError, FieldError, FieldResult, IntoFieldError};
680680
use parser::SourcePosition;
681681
use schema::model::RootNode;
682682
use types::scalars::EmptyMutation;
@@ -685,6 +685,23 @@ mod propagates_errors_to_nullable_fields {
685685
struct Schema;
686686
struct Inner;
687687

688+
enum CustomError {
689+
NotFound
690+
}
691+
692+
impl IntoFieldError for CustomError {
693+
fn into_field_error(self) -> FieldError {
694+
match self {
695+
CustomError::NotFound => FieldError::new(
696+
"Not Found",
697+
graphql_value!({
698+
"type": "NOT_FOUND"
699+
})
700+
)
701+
}
702+
}
703+
}
704+
688705
graphql_object!(Schema: () |&self| {
689706
field inner() -> Inner { Inner }
690707
field inners() -> Vec<Inner> { (0..5).map(|_| Inner).collect() }
@@ -696,6 +713,7 @@ mod propagates_errors_to_nullable_fields {
696713
field non_nullable_field() -> Inner { Inner }
697714
field nullable_error_field() -> FieldResult<Option<&str>> { Err("Error for nullableErrorField")? }
698715
field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? }
716+
field custom_error_field() -> Result<&str, CustomError> { Err(CustomError::NotFound) }
699717
});
700718

701719
#[test]
@@ -747,6 +765,29 @@ mod propagates_errors_to_nullable_fields {
747765
);
748766
}
749767

768+
#[test]
769+
fn custom_error_first_level() {
770+
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
771+
let doc = r"{ inner { customErrorField } }";
772+
773+
let vars = vec![].into_iter().collect();
774+
775+
let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed");
776+
777+
println!("Result: {:?}", result);
778+
779+
assert_eq!(result, graphql_value!(None));
780+
781+
assert_eq!(
782+
errs,
783+
vec![ExecutionError::new(
784+
SourcePosition::new(10, 0, 10),
785+
&["inner", "customErrorField"],
786+
FieldError::new("Not Found", graphql_value!({ "type": "NOT_FOUND" })),
787+
)]
788+
);
789+
}
790+
750791
#[test]
751792
fn nullable_nested_level() {
752793
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());

juniper/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ use validation::{validate_input_values, visit_all_rules, ValidatorContext};
152152

153153
pub use ast::{FromInputValue, InputValue, Selection, ToInputValue, Type};
154154
pub use executor::{Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
155-
FromContext, IntoResolvable, Registry, Variables};
155+
FromContext, IntoResolvable, Registry, Variables, IntoFieldError};
156156
pub use executor::{Applies, LookAheadArgument, LookAheadSelection, LookAheadValue, LookAheadMethods};
157157
pub use schema::model::RootNode;
158158
pub use types::base::{Arguments, GraphQLType, TypeKind};

0 commit comments

Comments
 (0)