From 98b662c88e4910d1832679b0b5e9b808c6a11c4f Mon Sep 17 00:00:00 2001 From: Michael Macias <zaeleus@gmail.com> Date: Fri, 27 Jan 2017 01:45:29 -0600 Subject: [PATCH] Accept explicit null input values in queries Explicit `null` values are valid for nullable input values. See <https://facebook.github.io/graphql/#sec-Types.Non-Null>. Fixes #23 --- src/executor_tests/variables.rs | 11 ++++++++ src/parser/value.rs | 2 +- src/types/utilities.rs | 2 +- src/validation/multi_visitor.rs | 7 +++++ .../rules/arguments_of_correct_type.rs | 27 +++++++++++++++++++ src/validation/traits.rs | 3 +++ src/validation/visitor.rs | 4 +-- 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/executor_tests/variables.rs b/src/executor_tests/variables.rs index 2ba53b10..a1baab7c 100644 --- a/src/executor_tests/variables.rs +++ b/src/executor_tests/variables.rs @@ -338,6 +338,17 @@ fn allow_nullable_inputs_to_be_omitted_in_variable() { }); } +#[test] +fn allow_nullable_inputs_to_be_explicitly_null() { + run_query( + r#"{ fieldWithNullableStringInput(input: null) }"#, + |result| { + assert_eq!( + result.get("fieldWithNullableStringInput"), + Some(&Value::string(r#"None"#))); + }); +} + #[test] fn allow_nullable_inputs_to_be_set_to_null_in_variable() { run_variable_query( diff --git a/src/parser/value.rs b/src/parser/value.rs index e7aa912a..cda1607d 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -24,7 +24,7 @@ pub fn parse_value_literal<'a>(parser: &mut Parser<'a>, is_const: bool) -> Parse Spanning { item: Token::Name("false"), .. } => Ok(parser.next().map(|_| InputValue::boolean(false))), Spanning { item: Token::Name("null"), .. } => - Err(parser.next().map(ParseError::UnexpectedToken)), + Ok(parser.next().map(|_| InputValue::null())), Spanning { item: Token::Name(name), .. } => Ok(parser.next().map(|_| InputValue::enum_value(name.to_owned()))), _ => Err(parser.next().map(ParseError::UnexpectedToken)), diff --git a/src/types/utilities.rs b/src/types/utilities.rs index d2b16dab..c957d5a4 100644 --- a/src/types/utilities.rs +++ b/src/types/utilities.rs @@ -21,7 +21,7 @@ pub fn is_valid_literal_value(schema: &SchemaType, arg_type: &TypeType, arg_valu } TypeType::Concrete(t) => { match *arg_value { - ref v @ InputValue::Null | + InputValue::Null => true, ref v @ InputValue::Int(_) | ref v @ InputValue::Float(_) | ref v @ InputValue::String(_) | diff --git a/src/validation/multi_visitor.rs b/src/validation/multi_visitor.rs index bda0e43c..f0e3de8d 100644 --- a/src/validation/multi_visitor.rs +++ b/src/validation/multi_visitor.rs @@ -95,6 +95,13 @@ impl<'a> Visitor<'a> for MultiVisitor<'a> { self.visit_all(|v| v.exit_inline_fragment(ctx, f)); } + fn enter_null_value(&mut self, ctx: &mut ValidatorContext<'a>, n: Spanning<()>) { + self.visit_all(|v| v.enter_null_value(ctx, n.clone())); + } + fn exit_null_value(&mut self, ctx: &mut ValidatorContext<'a>, n: Spanning<()>) { + self.visit_all(|v| v.exit_null_value(ctx, n.clone())); + } + fn enter_int_value(&mut self, ctx: &mut ValidatorContext<'a>, i: Spanning<i64>) { self.visit_all(|v| v.enter_int_value(ctx, i.clone())); } diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index 1cffe445..510f593f 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -64,6 +64,33 @@ mod tests { use parser::SourcePosition; use validation::{RuleError, expect_passes_rule, expect_fails_rule}; + #[test] + fn good_null_value() { + expect_passes_rule(factory, r#" + { + complicatedArgs { + intArgField(intArg: null) + } + } + "#); + } + + #[test] + fn null_into_int() { + expect_fails_rule(factory, r#" + { + complicatedArgs { + nonNullIntArgField(nonNullIntArg: null) + } + } + "#, + &[ + RuleError::new(&error_message("nonNullIntArg", "Int!"), &[ + SourcePosition::new(97, 3, 50), + ]) + ]); + } + #[test] fn good_int_value() { expect_passes_rule(factory, r#" diff --git a/src/validation/traits.rs b/src/validation/traits.rs index aaec53c4..8b97ff8e 100644 --- a/src/validation/traits.rs +++ b/src/validation/traits.rs @@ -36,6 +36,9 @@ pub trait Visitor<'a> { fn enter_inline_fragment(&mut self, _: &mut ValidatorContext<'a>, _: &'a Spanning<InlineFragment>) {} fn exit_inline_fragment(&mut self, _: &mut ValidatorContext<'a>, _: &'a Spanning<InlineFragment>) {} + fn enter_null_value(&mut self, _: &mut ValidatorContext<'a>, _: Spanning<()>) {} + fn exit_null_value(&mut self, _: &mut ValidatorContext<'a>, _: Spanning<()>) {} + fn enter_int_value(&mut self, _: &mut ValidatorContext<'a>, _: Spanning<i64>) {} fn exit_int_value(&mut self, _: &mut ValidatorContext<'a>, _: Spanning<i64>) {} diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 7076a331..856be7a3 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -226,7 +226,7 @@ fn enter_input_value<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<' let end = &input_value.end; match input_value.item { - Null => panic!("null values can't appear in the AST"), + Null => v.enter_null_value(ctx, Spanning::start_end(start, end, ())), Int(ref i) => v.enter_int_value(ctx, Spanning::start_end(start, end, *i)), Float(ref f) => v.enter_float_value(ctx, Spanning::start_end(start, end, *f)), String(ref s) => v.enter_string_value(ctx, Spanning::start_end(start, end, s)), @@ -245,7 +245,7 @@ fn exit_input_value<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a let end = &input_value.end; match input_value.item { - Null => panic!("null values can't appear in the AST"), + Null => v.exit_null_value(ctx, Spanning::start_end(start, end, ())), Int(ref i) => v.exit_int_value(ctx, Spanning::start_end(start, end, *i)), Float(ref f) => v.exit_float_value(ctx, Spanning::start_end(start, end, *f)), String(ref s) => v.exit_string_value(ctx, Spanning::start_end(start, end, s)),