Skip to content
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

Support for null values #683

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions language/ast/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Up @@ -27,6 +27,7 @@ const (
ListValue = "ListValue"
ObjectValue = "ObjectValue"
ObjectField = "ObjectField"
NullValue = "NullValue"

// Directives
Directive = "Directive"
Expand Down
12 changes: 10 additions & 2 deletions language/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
17 changes: 8 additions & 9 deletions language/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 := `
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions language/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) {
Expand Down
30 changes: 30 additions & 0 deletions scalars.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
},
Expand All @@ -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()
Expand All @@ -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{}
Expand Down Expand Up @@ -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
},
Expand Down