Skip to content

Commit 7b695c4

Browse files
committed
Propagate errors to closest nullable parent field
This also fixes a bug where context switching on optional values did not properly work.
1 parent 3c27b21 commit 7b695c4

File tree

6 files changed

+295
-61
lines changed

6 files changed

+295
-61
lines changed

juniper/src/executor.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for (&'a T::Context, T) {
200200
}
201201
}
202202

203-
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for Option<(&'a T::Context, T)> {
204-
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
205-
Ok(self)
203+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, Option<T>, C> for Option<(&'a T::Context, T)> {
204+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>> {
205+
Ok(self.map(|(ctx, v)| (ctx, Some(v))))
206206
}
207207
}
208208

@@ -212,9 +212,9 @@ impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<(&'a T::Con
212212
}
213213
}
214214

215-
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<Option<(&'a T::Context, T)>> {
216-
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
217-
self
215+
impl<'a, T: GraphQLType, C> IntoResolvable<'a, Option<T>, C> for FieldResult<Option<(&'a T::Context, T)>> {
216+
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>> {
217+
self.map(|o| o.map(|(ctx, v)| (ctx, Some(v))))
218218
}
219219
}
220220

juniper/src/executor_tests/executor.rs

Lines changed: 156 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -297,12 +297,11 @@ mod dynamic_context_switching {
297297
}
298298

299299
#[test]
300-
fn test_res() {
300+
fn test_res_success() {
301301
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
302302
let doc = r"
303303
{
304304
first: itemRes(key: 0) { value }
305-
missing: itemRes(key: 2) { value }
306305
}
307306
";
308307

@@ -318,13 +317,7 @@ mod dynamic_context_switching {
318317

319318
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx).expect("Execution failed");
320319

321-
assert_eq!(errs, vec![
322-
ExecutionError::new(
323-
SourcePosition::new(70, 3, 12),
324-
&["missing"],
325-
FieldError::new("Could not find key 2", Value::null()),
326-
),
327-
]);
320+
assert_eq!(errs, vec![]);
328321

329322
println!("Result: {:?}", result);
330323

@@ -334,10 +327,43 @@ mod dynamic_context_switching {
334327
("first", Value::object(vec![
335328
("value", Value::string("First value")),
336329
].into_iter().collect())),
337-
("missing", Value::null()),
338330
].into_iter().collect()));
339331
}
340332

333+
#[test]
334+
fn test_res_fail() {
335+
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
336+
let doc = r"
337+
{
338+
missing: itemRes(key: 2) { value }
339+
}
340+
";
341+
342+
let vars = vec![].into_iter().collect();
343+
344+
let ctx = OuterContext {
345+
items: vec![
346+
(0, InnerContext { value: "First value".to_owned() }),
347+
(1, InnerContext { value: "Second value".to_owned() }),
348+
].into_iter()
349+
.collect(),
350+
};
351+
352+
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx).expect("Execution failed");
353+
354+
assert_eq!(errs, vec![
355+
ExecutionError::new(
356+
SourcePosition::new(25, 2, 12),
357+
&["missing"],
358+
FieldError::new("Could not find key 2", Value::null()),
359+
),
360+
]);
361+
362+
println!("Result: {:?}", result);
363+
364+
assert_eq!(result, Value::null());
365+
}
366+
341367
#[test]
342368
fn test_res_opt() {
343369
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
@@ -413,24 +439,31 @@ mod dynamic_context_switching {
413439
}
414440
}
415441

416-
mod nulls_out_errors {
442+
mod propagates_errors_to_nullable_fields {
417443
use value::Value;
418444
use schema::model::RootNode;
419445
use executor::{ExecutionError, FieldError, FieldResult};
420446
use parser::SourcePosition;
421447
use types::scalars::EmptyMutation;
422448

423449
struct Schema;
450+
struct Inner;
424451

425452
graphql_object!(Schema: () |&self| {
426-
field sync() -> FieldResult<&str> { Ok("sync") }
427-
field sync_error() -> FieldResult<&str> { Err("Error for syncError")? }
453+
field inner() -> Inner { Inner }
454+
});
455+
456+
graphql_object!(Inner: () |&self| {
457+
field nullable_field() -> Option<Inner> { Some(Inner) }
458+
field non_nullable_field() -> Inner { Inner }
459+
field nullable_error_field() -> FieldResult<Option<&str>> { Err("Error for nullableErrorField")? }
460+
field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? }
428461
});
429462

430463
#[test]
431-
fn test() {
464+
fn nullable_first_level() {
432465
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
433-
let doc = r"{ sync, syncError }";
466+
let doc = r"{ inner { nullableErrorField } }";
434467

435468
let vars = vec![].into_iter().collect();
436469

@@ -440,18 +473,119 @@ mod nulls_out_errors {
440473

441474
assert_eq!(
442475
result,
443-
Value::object(vec![
444-
("sync", Value::string("sync")),
445-
("syncError", Value::null()),
446-
].into_iter().collect()));
476+
graphql_value!({ "inner": { "nullableErrorField": None } }));
477+
478+
assert_eq!(
479+
errs,
480+
vec![
481+
ExecutionError::new(
482+
SourcePosition::new(10, 0, 10),
483+
&["inner", "nullableErrorField"],
484+
FieldError::new("Error for nullableErrorField", Value::null()),
485+
),
486+
]);
487+
}
488+
489+
#[test]
490+
fn non_nullable_first_level() {
491+
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
492+
let doc = r"{ inner { nonNullableErrorField } }";
493+
494+
let vars = vec![].into_iter().collect();
495+
496+
let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed");
497+
498+
println!("Result: {:?}", result);
499+
500+
assert_eq!(
501+
result,
502+
graphql_value!(None));
503+
504+
assert_eq!(
505+
errs,
506+
vec![
507+
ExecutionError::new(
508+
SourcePosition::new(10, 0, 10),
509+
&["inner", "nonNullableErrorField"],
510+
FieldError::new("Error for nonNullableErrorField", Value::null()),
511+
),
512+
]);
513+
}
514+
515+
#[test]
516+
fn nullable_nested_level() {
517+
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
518+
let doc = r"{ inner { nullableField { nonNullableErrorField } } }";
519+
520+
let vars = vec![].into_iter().collect();
521+
522+
let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed");
523+
524+
println!("Result: {:?}", result);
525+
526+
assert_eq!(
527+
result,
528+
graphql_value!({ "inner": { "nullableField": None } }));
529+
530+
assert_eq!(
531+
errs,
532+
vec![
533+
ExecutionError::new(
534+
SourcePosition::new(26, 0, 26),
535+
&["inner", "nullableField", "nonNullableErrorField"],
536+
FieldError::new("Error for nonNullableErrorField", Value::null()),
537+
),
538+
]);
539+
}
540+
541+
#[test]
542+
fn non_nullable_nested_level() {
543+
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
544+
let doc = r"{ inner { nonNullableField { nonNullableErrorField } } }";
545+
546+
let vars = vec![].into_iter().collect();
547+
548+
let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed");
549+
550+
println!("Result: {:?}", result);
551+
552+
assert_eq!(
553+
result,
554+
graphql_value!(None));
555+
556+
assert_eq!(
557+
errs,
558+
vec![
559+
ExecutionError::new(
560+
SourcePosition::new(29, 0, 29),
561+
&["inner", "nonNullableField", "nonNullableErrorField"],
562+
FieldError::new("Error for nonNullableErrorField", Value::null()),
563+
),
564+
]);
565+
}
566+
567+
#[test]
568+
fn nullable_innermost() {
569+
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
570+
let doc = r"{ inner { nonNullableField { nullableErrorField } } }";
571+
572+
let vars = vec![].into_iter().collect();
573+
574+
let (result, errs) = ::execute(doc, None, &schema, &vars, &()).expect("Execution failed");
575+
576+
println!("Result: {:?}", result);
577+
578+
assert_eq!(
579+
result,
580+
graphql_value!({ "inner": { "nonNullableField": { "nullableErrorField": None } } }));
447581

448582
assert_eq!(
449583
errs,
450584
vec![
451585
ExecutionError::new(
452-
SourcePosition::new(8, 0, 8),
453-
&["syncError"],
454-
FieldError::new("Error for syncError", Value::null()),
586+
SourcePosition::new(29, 0, 29),
587+
&["inner", "nonNullableField", "nullableErrorField"],
588+
FieldError::new("Error for nullableErrorField", Value::null()),
455589
),
456590
]);
457591
}

juniper/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ extern crate uuid;
138138

139139
use std::borrow::Cow;
140140

141+
#[macro_use]
142+
mod value;
141143
#[macro_use]
142144
mod macros;
143145
mod ast;
144146
pub mod parser;
145-
mod value;
146147
mod types;
147148
mod schema;
148149
mod validation;

0 commit comments

Comments
 (0)