Skip to content

Support for null values #683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions language/ast/node.go
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ var _ Node = (*EnumValue)(nil)
var _ Node = (*ListValue)(nil)
var _ Node = (*ObjectValue)(nil)
var _ Node = (*ObjectField)(nil)
var _ Node = (*NullValue)(nil)
var _ Node = (*Directive)(nil)
var _ Node = (*Named)(nil)
var _ Node = (*List)(nil)
29 changes: 29 additions & 0 deletions language/ast/values.go
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ var _ Value = (*BooleanValue)(nil)
var _ Value = (*EnumValue)(nil)
var _ Value = (*ListValue)(nil)
var _ Value = (*ObjectValue)(nil)
var _ Value = (*NullValue)(nil)

// Variable implements Node, Value
type Variable struct {
@@ -300,3 +301,31 @@ func (f *ObjectField) GetLoc() *Location {
func (f *ObjectField) GetValue() interface{} {
return f.Value
}

// NullValue implements Node, Value
type NullValue struct {
Kind string
Loc *Location
}

func (n *NullValue) GetKind() string {
return n.Kind
}

func (n *NullValue) GetLoc() *Location {
return n.Loc
}

func (n *NullValue) GetValue() interface{} {
return nil
}

func NewNullValue(v *NullValue) *NullValue {
if v == nil {
v = &NullValue{}
}
return &NullValue{
Kind: kinds.NullValue,
Loc: v.Loc,
}
}
1 change: 1 addition & 0 deletions language/kinds/kinds.go
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ const (
ListValue = "ListValue"
ObjectValue = "ObjectValue"
ObjectField = "ObjectField"
NullValue = "NullValue"

// Directives
Directive = "Directive"
12 changes: 10 additions & 2 deletions language/parser/parser.go
Original file line number Diff line number Diff line change
@@ -606,7 +606,14 @@ func parseValueLiteral(parser *Parser, isConst bool) (ast.Value, error) {
Value: value,
Loc: loc(parser, token.Start),
}), nil
} else if token.Value != "null" {
} else if token.Value == "null" {
if err := advance(parser); err != nil {
return nil, err
}
return ast.NewNullValue(&ast.NullValue{
Loc: loc(parser, token.Start),
}), nil
} else {
if err := advance(parser); err != nil {
return nil, err
}
@@ -1562,7 +1569,8 @@ func unexpectedEmpty(parser *Parser, beginLoc int, openKind, closeKind lexer.Tok
return gqlerrors.NewSyntaxError(parser.Source, beginLoc, description)
}

// Returns list of parse nodes, determined by
// Returns list of parse nodes, determined by
//
// the parseFn. This list begins with a lex token of openKind
// and ends with a lex token of closeKind. Advances the parser
// to the next lex token after the closing token.
17 changes: 8 additions & 9 deletions language/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -183,15 +183,6 @@ func TestDoesNotAcceptFragmentsSpreadOfOn(t *testing.T) {
testErrorMessage(t, test)
}

func TestDoesNotAllowNullAsValue(t *testing.T) {
test := errorMessageTest{
`{ fieldWithNullableStringInput(input: null) }'`,
`Syntax Error GraphQL (1:39) Unexpected Name "null"`,
false,
}
testErrorMessage(t, test)
}

func TestParsesMultiByteCharacters_Unicode(t *testing.T) {

doc := `
@@ -498,6 +489,14 @@ func TestParsesEnumValueDefinitionWithDescription(t *testing.T) {
}
}

func TestNullIsAllowedAsValue(t *testing.T) {
source := `{ fieldWithNullableStringInput(input: null) }`
_, err := Parse(ParseParams{Source: source})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

func TestDefinitionsWithDescriptions(t *testing.T) {
testCases := []struct {
name string
10 changes: 10 additions & 0 deletions language/printer/printer.go
Original file line number Diff line number Diff line change
@@ -428,6 +428,16 @@ var printDocASTReducer = map[string]visitor.VisitFunc{
}
return visitor.ActionNoChange, nil
},
"NullValue": func(p visitor.VisitFuncParams) (string, interface{}) {
const nullStr = "null"
switch p.Node.(type) {
case *ast.NullValue:
return visitor.ActionUpdate, nullStr
case map[string]interface{}: //TODO: not sure if this is necessary
return visitor.ActionUpdate, nullStr
}
return visitor.ActionNoChange, nil
},

// Directive
"Directive": func(p visitor.VisitFuncParams) (string, interface{}) {
30 changes: 30 additions & 0 deletions scalars.go
Original file line number Diff line number Diff line change
@@ -15,6 +15,9 @@ import (
// n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
// they are internally represented as IEEE 754 doubles.
func coerceInt(value interface{}) interface{} {
if value == nil {
return nil
}
switch value := value.(type) {
case bool:
if value == true {
@@ -162,12 +165,17 @@ var Int = NewScalar(ScalarConfig{
if intValue, err := strconv.Atoi(valueAST.Value); err == nil {
return intValue
}
case *ast.NullValue:
return nil
}
return nil
},
})

func coerceFloat(value interface{}) interface{} {
if value == nil {
return nil
}
switch value := value.(type) {
case bool:
if value == true {
@@ -299,12 +307,17 @@ var Float = NewScalar(ScalarConfig{
if floatValue, err := strconv.ParseFloat(valueAST.Value, 64); err == nil {
return floatValue
}
case *ast.NullValue:
return nil
}
return nil
},
})

func coerceString(value interface{}) interface{} {
if value == nil {
return nil
}
if v, ok := value.(*string); ok {
if v == nil {
return nil
@@ -326,12 +339,17 @@ var String = NewScalar(ScalarConfig{
switch valueAST := valueAST.(type) {
case *ast.StringValue:
return valueAST.Value
case *ast.NullValue:
return nil
}
return nil
},
})

func coerceBool(value interface{}) interface{} {
if value == nil {
return nil
}
switch value := value.(type) {
case bool:
return value
@@ -485,6 +503,8 @@ var Boolean = NewScalar(ScalarConfig{
switch valueAST := valueAST.(type) {
case *ast.BooleanValue:
return valueAST.Value
case *ast.NullValue:
return nil
}
return nil
},
@@ -506,12 +526,17 @@ var ID = NewScalar(ScalarConfig{
return valueAST.Value
case *ast.StringValue:
return valueAST.Value
case *ast.NullValue:
return nil
}
return nil
},
})

func serializeDateTime(value interface{}) interface{} {
if value == nil {
return nil
}
switch value := value.(type) {
case time.Time:
buff, err := value.MarshalText()
@@ -531,6 +556,9 @@ func serializeDateTime(value interface{}) interface{} {
}

func unserializeDateTime(value interface{}) interface{} {
if value == nil {
return nil
}
switch value := value.(type) {
case []byte:
t := time.Time{}
@@ -564,6 +592,8 @@ var DateTime = NewScalar(ScalarConfig{
switch valueAST := valueAST.(type) {
case *ast.StringValue:
return unserializeDateTime(valueAST.Value)
case *ast.NullValue:
return nil
}
return nil
},