Propagate errors to closest nullable parent field
This also fixes a bug where context switching on optional values did not properly work.
This commit is contained in:
parent
3c27b219bd
commit
7b695c406c
6 changed files with 295 additions and 61 deletions
|
@ -200,9 +200,9 @@ impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for (&'a T::Context, T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for Option<(&'a T::Context, T)> {
|
impl<'a, T: GraphQLType, C> IntoResolvable<'a, Option<T>, C> for Option<(&'a T::Context, T)> {
|
||||||
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
|
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>> {
|
||||||
Ok(self)
|
Ok(self.map(|(ctx, v)| (ctx, Some(v))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,9 +212,9 @@ impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<(&'a T::Con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: GraphQLType, C> IntoResolvable<'a, T, C> for FieldResult<Option<(&'a T::Context, T)>> {
|
impl<'a, T: GraphQLType, C> IntoResolvable<'a, Option<T>, C> for FieldResult<Option<(&'a T::Context, T)>> {
|
||||||
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>> {
|
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>> {
|
||||||
self
|
self.map(|o| o.map(|(ctx, v)| (ctx, Some(v))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -297,11 +297,44 @@ mod dynamic_context_switching {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_res() {
|
fn test_res_success() {
|
||||||
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
|
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
|
||||||
let doc = r"
|
let doc = r"
|
||||||
{
|
{
|
||||||
first: itemRes(key: 0) { value }
|
first: itemRes(key: 0) { value }
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let ctx = OuterContext {
|
||||||
|
items: vec![
|
||||||
|
(0, InnerContext { value: "First value".to_owned() }),
|
||||||
|
(1, InnerContext { value: "Second value".to_owned() }),
|
||||||
|
].into_iter()
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &ctx).expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, vec![]);
|
||||||
|
|
||||||
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("first", Value::object(vec![
|
||||||
|
("value", Value::string("First value")),
|
||||||
|
].into_iter().collect())),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_res_fail() {
|
||||||
|
let schema = RootNode::new(Schema, EmptyMutation::<OuterContext>::new());
|
||||||
|
let doc = r"
|
||||||
|
{
|
||||||
missing: itemRes(key: 2) { value }
|
missing: itemRes(key: 2) { value }
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
@ -320,7 +353,7 @@ mod dynamic_context_switching {
|
||||||
|
|
||||||
assert_eq!(errs, vec![
|
assert_eq!(errs, vec![
|
||||||
ExecutionError::new(
|
ExecutionError::new(
|
||||||
SourcePosition::new(70, 3, 12),
|
SourcePosition::new(25, 2, 12),
|
||||||
&["missing"],
|
&["missing"],
|
||||||
FieldError::new("Could not find key 2", Value::null()),
|
FieldError::new("Could not find key 2", Value::null()),
|
||||||
),
|
),
|
||||||
|
@ -328,14 +361,7 @@ mod dynamic_context_switching {
|
||||||
|
|
||||||
println!("Result: {:?}", result);
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(result, Value::null());
|
||||||
result,
|
|
||||||
Value::object(vec![
|
|
||||||
("first", Value::object(vec![
|
|
||||||
("value", Value::string("First value")),
|
|
||||||
].into_iter().collect())),
|
|
||||||
("missing", Value::null()),
|
|
||||||
].into_iter().collect()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -413,7 +439,7 @@ mod dynamic_context_switching {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod nulls_out_errors {
|
mod propagates_errors_to_nullable_fields {
|
||||||
use value::Value;
|
use value::Value;
|
||||||
use schema::model::RootNode;
|
use schema::model::RootNode;
|
||||||
use executor::{ExecutionError, FieldError, FieldResult};
|
use executor::{ExecutionError, FieldError, FieldResult};
|
||||||
|
@ -421,16 +447,23 @@ mod nulls_out_errors {
|
||||||
use types::scalars::EmptyMutation;
|
use types::scalars::EmptyMutation;
|
||||||
|
|
||||||
struct Schema;
|
struct Schema;
|
||||||
|
struct Inner;
|
||||||
|
|
||||||
graphql_object!(Schema: () |&self| {
|
graphql_object!(Schema: () |&self| {
|
||||||
field sync() -> FieldResult<&str> { Ok("sync") }
|
field inner() -> Inner { Inner }
|
||||||
field sync_error() -> FieldResult<&str> { Err("Error for syncError")? }
|
});
|
||||||
|
|
||||||
|
graphql_object!(Inner: () |&self| {
|
||||||
|
field nullable_field() -> Option<Inner> { Some(Inner) }
|
||||||
|
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")? }
|
||||||
});
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn nullable_first_level() {
|
||||||
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
||||||
let doc = r"{ sync, syncError }";
|
let doc = r"{ inner { nullableErrorField } }";
|
||||||
|
|
||||||
let vars = vec![].into_iter().collect();
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
@ -440,18 +473,119 @@ mod nulls_out_errors {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Value::object(vec![
|
graphql_value!({ "inner": { "nullableErrorField": None } }));
|
||||||
("sync", Value::string("sync")),
|
|
||||||
("syncError", Value::null()),
|
|
||||||
].into_iter().collect()));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errs,
|
errs,
|
||||||
vec![
|
vec![
|
||||||
ExecutionError::new(
|
ExecutionError::new(
|
||||||
SourcePosition::new(8, 0, 8),
|
SourcePosition::new(10, 0, 10),
|
||||||
&["syncError"],
|
&["inner", "nullableErrorField"],
|
||||||
FieldError::new("Error for syncError", Value::null()),
|
FieldError::new("Error for nullableErrorField", Value::null()),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_nullable_first_level() {
|
||||||
|
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
||||||
|
let doc = r"{ inner { nonNullableErrorField } }";
|
||||||
|
|
||||||
|
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", "nonNullableErrorField"],
|
||||||
|
FieldError::new("Error for nonNullableErrorField", Value::null()),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nullable_nested_level() {
|
||||||
|
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
||||||
|
let doc = r"{ inner { nullableField { nonNullableErrorField } } }";
|
||||||
|
|
||||||
|
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!({ "inner": { "nullableField": None } }));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
errs,
|
||||||
|
vec![
|
||||||
|
ExecutionError::new(
|
||||||
|
SourcePosition::new(26, 0, 26),
|
||||||
|
&["inner", "nullableField", "nonNullableErrorField"],
|
||||||
|
FieldError::new("Error for nonNullableErrorField", Value::null()),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_nullable_nested_level() {
|
||||||
|
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
||||||
|
let doc = r"{ inner { nonNullableField { nonNullableErrorField } } }";
|
||||||
|
|
||||||
|
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(29, 0, 29),
|
||||||
|
&["inner", "nonNullableField", "nonNullableErrorField"],
|
||||||
|
FieldError::new("Error for nonNullableErrorField", Value::null()),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nullable_innermost() {
|
||||||
|
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
||||||
|
let doc = r"{ inner { nonNullableField { nullableErrorField } } }";
|
||||||
|
|
||||||
|
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!({ "inner": { "nonNullableField": { "nullableErrorField": None } } }));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
errs,
|
||||||
|
vec![
|
||||||
|
ExecutionError::new(
|
||||||
|
SourcePosition::new(29, 0, 29),
|
||||||
|
&["inner", "nonNullableField", "nullableErrorField"],
|
||||||
|
FieldError::new("Error for nullableErrorField", Value::null()),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,11 +138,12 @@ extern crate uuid;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod value;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod ast;
|
mod ast;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
mod value;
|
|
||||||
mod types;
|
mod types;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
|
@ -5,6 +5,7 @@ use ast::InputValue;
|
||||||
use value::Value;
|
use value::Value;
|
||||||
use schema::model::RootNode;
|
use schema::model::RootNode;
|
||||||
use types::scalars::EmptyMutation;
|
use types::scalars::EmptyMutation;
|
||||||
|
use executor::{FieldResult, Context};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ Syntax to validate:
|
||||||
* Optional Generics/lifetimes
|
* Optional Generics/lifetimes
|
||||||
* Custom name vs. default name
|
* Custom name vs. default name
|
||||||
* Optional commas between items
|
* Optional commas between items
|
||||||
|
* Nullable/fallible context switching
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -102,6 +104,22 @@ graphql_object!(CommasOnMeta: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct InnerContext;
|
||||||
|
const INNER_CONTEXT: InnerContext = InnerContext;
|
||||||
|
impl Context for InnerContext {}
|
||||||
|
|
||||||
|
struct InnerType;
|
||||||
|
graphql_object!(InnerType: InnerContext |&self| {
|
||||||
|
});
|
||||||
|
|
||||||
|
struct CtxSwitcher;
|
||||||
|
graphql_object!(CtxSwitcher: () |&self| {
|
||||||
|
field ctx_switch_always() -> (&InnerContext, InnerType) { (&INNER_CONTEXT, InnerType) }
|
||||||
|
field ctx_switch_opt() -> Option<(&InnerContext, InnerType)> { Some((&INNER_CONTEXT, InnerType)) }
|
||||||
|
field ctx_switch_res() -> FieldResult<(&InnerContext, InnerType)> { Ok((&INNER_CONTEXT, InnerType)) }
|
||||||
|
field ctx_switch_res_opt() -> FieldResult<Option<(&InnerContext, InnerType)>> { Ok(Some((&INNER_CONTEXT, InnerType))) }
|
||||||
|
});
|
||||||
|
|
||||||
graphql_object!(<'a> Root: () as "Root" |&self| {
|
graphql_object!(<'a> Root: () as "Root" |&self| {
|
||||||
field custom_name() -> CustomName { CustomName {} }
|
field custom_name() -> CustomName { CustomName {} }
|
||||||
|
|
||||||
|
@ -114,6 +132,8 @@ graphql_object!(<'a> Root: () as "Root" |&self| {
|
||||||
|
|
||||||
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} }
|
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} }
|
||||||
field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} }
|
field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} }
|
||||||
|
|
||||||
|
field ctx_switcher() -> CtxSwitcher { CtxSwitcher {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,6 +148,14 @@ where
|
||||||
description
|
description
|
||||||
fields(includeDeprecated: true) {
|
fields(includeDeprecated: true) {
|
||||||
name
|
name
|
||||||
|
type {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
ofType {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
interfaces {
|
interfaces {
|
||||||
name
|
name
|
||||||
|
@ -172,9 +200,10 @@ fn introspect_custom_name() {
|
||||||
assert_eq!(object.get("description"), Some(&Value::null()));
|
assert_eq!(object.get("description"), Some(&Value::null()));
|
||||||
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,9 +214,10 @@ fn introspect_with_lifetime() {
|
||||||
assert_eq!(object.get("description"), Some(&Value::null()));
|
assert_eq!(object.get("description"), Some(&Value::null()));
|
||||||
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +228,10 @@ fn introspect_with_generics() {
|
||||||
assert_eq!(object.get("description"), Some(&Value::null()));
|
assert_eq!(object.get("description"), Some(&Value::null()));
|
||||||
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
assert_eq!(object.get("interfaces"), Some(&Value::list(vec![])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,9 +247,10 @@ fn introspect_description_first() {
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,9 +266,10 @@ fn introspect_fields_first() {
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,9 +285,10 @@ fn introspect_interfaces_first() {
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,9 +304,10 @@ fn introspect_commas_with_trailing() {
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,8 +323,56 @@ fn introspect_commas_on_meta() {
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
assert!(fields.contains(&Value::object(vec![
|
assert!(fields.contains(&graphql_value!({
|
||||||
("name", Value::string("simple")),
|
"name": "simple",
|
||||||
].into_iter().collect())));
|
"type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } }
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn introspect_ctx_switch() {
|
||||||
|
run_type_info_query("CtxSwitcher", |_, fields| {
|
||||||
|
assert!(fields.contains(&graphql_value!({
|
||||||
|
"name": "ctxSwitchAlways",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": None,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "InnerType",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
assert!(fields.contains(&graphql_value!({
|
||||||
|
"name": "ctxSwitchOpt",
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "InnerType",
|
||||||
|
"ofType": None
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
assert!(fields.contains(&graphql_value!({
|
||||||
|
"name": "ctxSwitchRes",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": None,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "InnerType",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
assert!(fields.contains(&graphql_value!({
|
||||||
|
"name": "ctxSwitchResOpt",
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "InnerType",
|
||||||
|
"ofType": None
|
||||||
|
}
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,8 +307,11 @@ pub trait GraphQLType: Sized {
|
||||||
) -> Value {
|
) -> Value {
|
||||||
if let Some(selection_set) = selection_set {
|
if let Some(selection_set) = selection_set {
|
||||||
let mut result = OrderMap::new();
|
let mut result = OrderMap::new();
|
||||||
resolve_selection_set_into(self, info, selection_set, executor, &mut result);
|
if resolve_selection_set_into(self, info, selection_set, executor, &mut result) {
|
||||||
Value::object(result)
|
Value::object(result)
|
||||||
|
} else {
|
||||||
|
Value::null()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("resolve() must be implemented by non-object output types");
|
panic!("resolve() must be implemented by non-object output types");
|
||||||
}
|
}
|
||||||
|
@ -321,7 +324,7 @@ fn resolve_selection_set_into<T, CtxT>(
|
||||||
selection_set: &[Selection],
|
selection_set: &[Selection],
|
||||||
executor: &Executor<CtxT>,
|
executor: &Executor<CtxT>,
|
||||||
result: &mut OrderMap<String, Value>,
|
result: &mut OrderMap<String, Value>,
|
||||||
) where
|
) -> bool where
|
||||||
T: GraphQLType<Context = CtxT>,
|
T: GraphQLType<Context = CtxT>,
|
||||||
{
|
{
|
||||||
let meta_type = executor
|
let meta_type = executor
|
||||||
|
@ -388,9 +391,15 @@ fn resolve_selection_set_into<T, CtxT>(
|
||||||
);
|
);
|
||||||
|
|
||||||
match field_result {
|
match field_result {
|
||||||
|
Ok(Value::Null) if meta_field.field_type.is_non_null() => return false,
|
||||||
Ok(v) => merge_key_into(result, response_name, v),
|
Ok(v) => merge_key_into(result, response_name, v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
sub_exec.push_error_at(e, start_pos.clone());
|
sub_exec.push_error_at(e, start_pos.clone());
|
||||||
|
|
||||||
|
if meta_field.field_type.is_non_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
result.insert((*response_name).to_owned(), Value::null());
|
result.insert((*response_name).to_owned(), Value::null());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,13 +415,15 @@ fn resolve_selection_set_into<T, CtxT>(
|
||||||
.fragment_by_name(spread.name.item)
|
.fragment_by_name(spread.name.item)
|
||||||
.expect("Fragment could not be found");
|
.expect("Fragment could not be found");
|
||||||
|
|
||||||
resolve_selection_set_into(
|
if !resolve_selection_set_into(
|
||||||
instance,
|
instance,
|
||||||
info,
|
info,
|
||||||
&fragment.selection_set[..],
|
&fragment.selection_set[..],
|
||||||
executor,
|
executor,
|
||||||
result,
|
result,
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Selection::InlineFragment(Spanning {
|
Selection::InlineFragment(Spanning {
|
||||||
item: ref fragment,
|
item: ref fragment,
|
||||||
|
@ -442,17 +453,21 @@ fn resolve_selection_set_into<T, CtxT>(
|
||||||
sub_exec.push_error_at(e, start_pos.clone());
|
sub_exec.push_error_at(e, start_pos.clone());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve_selection_set_into(
|
if !resolve_selection_set_into(
|
||||||
instance,
|
instance,
|
||||||
info,
|
info,
|
||||||
&fragment.selection_set[..],
|
&fragment.selection_set[..],
|
||||||
&sub_exec,
|
&sub_exec,
|
||||||
result,
|
result,
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_excluded(directives: &Option<Vec<Spanning<Directive>>>, vars: &Variables) -> bool {
|
fn is_excluded(directives: &Option<Vec<Spanning<Directive>>>, vars: &Variables) -> bool {
|
||||||
|
|
|
@ -205,6 +205,7 @@ macro_rules! graphql_value {
|
||||||
$( ($key, graphql_value!($val)), )*
|
$( ($key, graphql_value!($val)), )*
|
||||||
].into_iter().collect())
|
].into_iter().collect())
|
||||||
};
|
};
|
||||||
|
(None) => ($crate::Value::null());
|
||||||
($e:expr) => ($crate::Value::from($e))
|
($e:expr) => ($crate::Value::from($e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +252,7 @@ mod tests {
|
||||||
Value::string("test")
|
Value::string("test")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graphql_value!((None as Option<String>)),
|
graphql_value!(None),
|
||||||
Value::null()
|
Value::null()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue