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)),