diff --git a/graphql/language/ast.py b/graphql/language/ast.py index 6fffae84..b19b393c 100644 --- a/graphql/language/ast.py +++ b/graphql/language/ast.py @@ -534,6 +534,32 @@ def __hash__(self): return id(self) +class NullValue(Value): + __slots__ = ('loc', 'value') + _fields = ('value',) + + def __init__(self, value=None, loc=None): + self.loc = loc + self.value = value + + def __eq__(self, other): + return isinstance(other, NullValue) + + def __repr__(self): + return ('NullValue(' + 'value={self.value!r}' + ')').format(self=self) + + def __copy__(self): + return type(self)( + self.value, + self.loc + ) + + def __hash__(self): + return id(self) + + class EnumValue(Value): __slots__ = ('loc', 'value',) _fields = ('value',) diff --git a/graphql/language/parser.py b/graphql/language/parser.py index 21adac91..21d43826 100644 --- a/graphql/language/parser.py +++ b/graphql/language/parser.py @@ -418,7 +418,11 @@ def parse_value_literal(parser, is_const): advance(parser) return ast.BooleanValue(value=token.value == 'true', loc=loc(parser, token.start)) - if token.value != 'null': + elif token.value in ('null', ): + advance(parser) + return ast.NullValue(loc=loc(parser, token.start)) + + else: advance(parser) return ast.EnumValue(value=token.value, loc=loc(parser, token.start)) diff --git a/graphql/language/printer.py b/graphql/language/printer.py index a1a6dd36..35c2d51e 100644 --- a/graphql/language/printer.py +++ b/graphql/language/printer.py @@ -81,6 +81,9 @@ def leave_StringValue(self, node, *args): def leave_BooleanValue(self, node, *args): return json.dumps(node.value) + def leave_NullValue(self, node, *args): + return json.dumps(node.value) + def leave_EnumValue(self, node, *args): return node.value diff --git a/graphql/language/tests/fixtures.py b/graphql/language/tests/fixtures.py index b16653c4..74b2ccda 100644 --- a/graphql/language/tests/fixtures.py +++ b/graphql/language/tests/fixtures.py @@ -53,7 +53,7 @@ } { - unnamed(truthy: true, falsey: false), + unnamed(truthy: true, falsey: false, nullish: null), query } """ diff --git a/graphql/language/tests/test_parser.py b/graphql/language/tests/test_parser.py index 3519cef5..47ebb3d3 100644 --- a/graphql/language/tests/test_parser.py +++ b/graphql/language/tests/test_parser.py @@ -77,13 +77,6 @@ def test_does_not_accept_fragments_spread_of_on(): assert 'Syntax Error GraphQL (1:9) Expected Name, found }' in excinfo.value.message -def test_does_not_allow_null_value(): - with raises(GraphQLSyntaxError) as excinfo: - parse('{ fieldWithNullableStringInput(input: null) }') - - assert 'Syntax Error GraphQL (1:39) Unexpected Name "null"' in excinfo.value.message - - def test_parses_multi_byte_characters(): result = parse(u''' # This comment has a \u0A0A multi-byte character. diff --git a/graphql/language/tests/test_printer.py b/graphql/language/tests/test_printer.py index 5d8ce7ea..ca4a6375 100644 --- a/graphql/language/tests/test_printer.py +++ b/graphql/language/tests/test_printer.py @@ -118,7 +118,7 @@ def test_prints_kitchen_sink(): } { - unnamed(truthy: true, falsey: false) + unnamed(truthy: true, falsey: false, nullish: null) query } ''' diff --git a/graphql/language/tests/test_visitor.py b/graphql/language/tests/test_visitor.py index 5ede5294..85f24d5e 100644 --- a/graphql/language/tests/test_visitor.py +++ b/graphql/language/tests/test_visitor.py @@ -562,6 +562,12 @@ def leave(self, node, key, parent, *args): ['enter', 'BooleanValue', 'value', 'Argument'], ['leave', 'BooleanValue', 'value', 'Argument'], ['leave', 'Argument', 1, None], + ['enter', 'Argument', 2, None], + ['enter', 'Name', 'name', 'Argument'], + ['leave', 'Name', 'name', 'Argument'], + ['enter', 'NullValue', 'value', 'Argument'], + ['leave', 'NullValue', 'value', 'Argument'], + ['leave', 'Argument', 2, None], ['leave', 'Field', 0, None], ['enter', 'Field', 1, None], ['enter', 'Name', 'name', 'Field'], diff --git a/graphql/language/visitor_meta.py b/graphql/language/visitor_meta.py index db2e6409..a3631de7 100644 --- a/graphql/language/visitor_meta.py +++ b/graphql/language/visitor_meta.py @@ -19,6 +19,7 @@ ast.FloatValue: (), ast.StringValue: (), ast.BooleanValue: (), + ast.NullValue: (), ast.EnumValue: (), ast.ListValue: ('values',), ast.ObjectValue: ('fields',),