Skip to content

Commit 8eafea7

Browse files
committed
WIP - Add null support
Code is mostly copy-pasted from the PR graphql-go#536
1 parent b2134d2 commit 8eafea7

File tree

7 files changed

+104
-22
lines changed

7 files changed

+104
-22
lines changed

language/ast/node.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var _ Node = (*IntValue)(nil)
2222
var _ Node = (*FloatValue)(nil)
2323
var _ Node = (*StringValue)(nil)
2424
var _ Node = (*BooleanValue)(nil)
25+
var _ Node = (*NullValue)(nil)
2526
var _ Node = (*EnumValue)(nil)
2627
var _ Node = (*ListValue)(nil)
2728
var _ Node = (*ObjectValue)(nil)

language/ast/values.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var _ Value = (*IntValue)(nil)
1616
var _ Value = (*FloatValue)(nil)
1717
var _ Value = (*StringValue)(nil)
1818
var _ Value = (*BooleanValue)(nil)
19+
var _ Value = (*NullValue)(nil)
1920
var _ Value = (*EnumValue)(nil)
2021
var _ Value = (*ListValue)(nil)
2122
var _ Value = (*ObjectValue)(nil)
@@ -172,6 +173,33 @@ func (v *BooleanValue) GetValue() interface{} {
172173
return v.Value
173174
}
174175

176+
type NullValue struct {
177+
Kind string
178+
Loc *Location
179+
Value interface{}
180+
}
181+
182+
func NewNullValue(v *NullValue) *NullValue {
183+
184+
return &NullValue{
185+
Kind: kinds.NullValue,
186+
Loc: v.Loc,
187+
Value: v.Value,
188+
}
189+
}
190+
191+
func (v *NullValue) GetKind() string {
192+
return v.Kind
193+
}
194+
195+
func (v *NullValue) GetLoc() *Location {
196+
return v.Loc
197+
}
198+
199+
func (v *NullValue) GetValue() interface{} {
200+
return nil
201+
}
202+
175203
// EnumValue implements Node, Value
176204
type EnumValue struct {
177205
Kind string

language/kinds/kinds.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const (
2323
FloatValue = "FloatValue"
2424
StringValue = "StringValue"
2525
BooleanValue = "BooleanValue"
26+
NullValue = "NullValue"
2627
EnumValue = "EnumValue"
2728
ListValue = "ListValue"
2829
ObjectValue = "ObjectValue"

language/parser/parser.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -614,15 +614,23 @@ func parseValueLiteral(parser *Parser, isConst bool) (ast.Value, error) {
614614
Value: value,
615615
Loc: loc(parser, token.Start),
616616
}), nil
617-
} else if token.Value != "null" {
617+
} else if token.Value == "null" {
618618
if err := advance(parser); err != nil {
619619
return nil, err
620620
}
621-
return ast.NewEnumValue(&ast.EnumValue{
622-
Value: token.Value,
621+
return ast.NewNullValue(&ast.NullValue{
622+
Value: nil,
623623
Loc: loc(parser, token.Start),
624624
}), nil
625625
}
626+
627+
if err := advance(parser); err != nil {
628+
return nil, err
629+
}
630+
return ast.NewEnumValue(&ast.EnumValue{
631+
Value: token.Value,
632+
Loc: loc(parser, token.Start),
633+
}), nil
626634
case lexer.DOLLAR:
627635
if !isConst {
628636
return parseVariable(parser)

language/printer/printer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,15 @@ var printDocASTReducer = map[string]visitor.VisitFunc{
388388
}
389389
return visitor.ActionNoChange, nil
390390
},
391+
"NullValue": func(p visitor.VisitFuncParams) (string, interface{}) {
392+
switch node := p.Node.(type) {
393+
case *ast.NullValue:
394+
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value)
395+
case map[string]interface{}:
396+
return visitor.ActionUpdate, getMapValueString(node, "Value")
397+
}
398+
return visitor.ActionNoChange, nil
399+
},
391400
"EnumValue": func(p visitor.VisitFuncParams) (string, interface{}) {
392401
switch node := p.Node.(type) {
393402
case *ast.EnumValue:

rules.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,6 +1730,10 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
17301730
return true, nil
17311731
}
17321732

1733+
if valueAST.GetKind() == kinds.NullValue {
1734+
return true, nil
1735+
}
1736+
17331737
// This function only tests literals, and assumes variables will provide
17341738
// values of the correct type.
17351739
if valueAST.GetKind() == kinds.Variable {
@@ -1742,7 +1746,7 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
17421746
if e := ttype.Error(); e != nil {
17431747
return false, []string{e.Error()}
17441748
}
1745-
if valueAST == nil {
1749+
if valueAST == nil || valueAST.GetKind() == kinds.NullValue {
17461750
if ttype.OfType.Name() != "" {
17471751
return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())}
17481752
}
@@ -1797,11 +1801,11 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
17971801
}
17981802
return (len(messagesReduce) == 0), messagesReduce
17991803
case *Scalar:
1800-
if isNullish(ttype.ParseLiteral(valueAST)) {
1804+
if !isNullish(ttype.ParseLiteral(valueAST)) {
18011805
return false, []string{fmt.Sprintf(`Expected type "%v", found %v.`, ttype.Name(), printer.Print(valueAST))}
18021806
}
18031807
case *Enum:
1804-
if isNullish(ttype.ParseLiteral(valueAST)) {
1808+
if !isNullish(ttype.ParseLiteral(valueAST)) {
18051809
return false, []string{fmt.Sprintf(`Expected type "%v", found %v.`, ttype.Name(), printer.Print(valueAST))}
18061810
}
18071811
}

values.go

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414
"github.com/graphql-go/graphql/language/printer"
1515
)
1616

17+
// Used to detect the difference between a "null" literal and not present
18+
type nullValue struct{}
19+
1720
// Prepares an object map of variableValues of the correct type based on the
1821
// provided variable definitions and arbitrary input. If the input cannot be
1922
// parsed to match the variable definitions, a GraphQLError will be returned.
@@ -27,7 +30,7 @@ func getVariableValues(
2730
continue
2831
}
2932
varName := defAST.Variable.Name.Value
30-
if varValue, err := getVariableValue(schema, defAST, inputs[varName]); err != nil {
33+
if varValue, err := getVariableValue(schema, defAST, getValueOrNull(inputs, varName)); err != nil {
3134
return values, err
3235
} else {
3336
values[varName] = varValue
@@ -36,6 +39,25 @@ func getVariableValues(
3639
return values, nil
3740
}
3841

42+
func getValueOrNull(values map[string]interface{}, name string) interface{} {
43+
if tmp, ok := values[name]; ok { // Is present
44+
if tmp == nil {
45+
return nullValue{} // Null value
46+
} else {
47+
return tmp
48+
}
49+
}
50+
return nil // Not present
51+
}
52+
53+
func addValueOrNull(values map[string]interface{}, name string, value interface{}) {
54+
if _, ok := value.(nullValue); ok { // Null value
55+
values[name] = nil
56+
} else if !isNullish(value) { // Not present
57+
values[name] = value
58+
}
59+
}
60+
3961
// Prepares an object map of argument values given a list of argument
4062
// definitions and list of argument AST nodes.
4163
func getArgumentValues(
@@ -60,9 +82,7 @@ func getArgumentValues(
6082
if tmp = valueFromAST(value, argDef.Type, variableValues); isNullish(tmp) {
6183
tmp = argDef.DefaultValue
6284
}
63-
if !isNullish(tmp) {
64-
results[argDef.PrivateName] = tmp
65-
}
85+
addValueOrNull(results, argDef.PrivateName, tmp)
6686
}
6787
return results
6888
}
@@ -97,7 +117,7 @@ func getVariableValue(schema Schema, definitionAST *ast.VariableDefinition, inpu
97117
}
98118
return coerceValue(ttype, input), nil
99119
}
100-
if isNullish(input) {
120+
if _, ok := input.(nullValue); ok || isNullish(input) {
101121
return "", gqlerrors.NewError(
102122
fmt.Sprintf(`Variable "$%v" of required type `+
103123
`"%v" was not provided.`, variable.Name.Value, printer.Print(definitionAST.Type)),
@@ -134,6 +154,11 @@ func coerceValue(ttype Input, value interface{}) interface{} {
134154
if isNullish(value) {
135155
return nil
136156
}
157+
158+
if _, ok := value.(nullValue); ok {
159+
return nullValue{}
160+
}
161+
137162
switch ttype := ttype.(type) {
138163
case *NonNull:
139164
return coerceValue(ttype.OfType, value)
@@ -156,13 +181,11 @@ func coerceValue(ttype Input, value interface{}) interface{} {
156181
}
157182

158183
for name, field := range ttype.Fields() {
159-
fieldValue := coerceValue(field.Type, valueMap[name])
184+
fieldValue := coerceValue(field.Type, getValueOrNull(valueMap, name))
160185
if isNullish(fieldValue) {
161186
fieldValue = field.DefaultValue
162187
}
163-
if !isNullish(fieldValue) {
164-
obj[name] = fieldValue
165-
}
188+
addValueOrNull(obj, name, fieldValue)
166189
}
167190
return obj
168191
case *Scalar:
@@ -212,7 +235,7 @@ func typeFromAST(schema Schema, inputTypeAST ast.Type) (Type, error) {
212235
// accepted for that type. This is primarily useful for validating the
213236
// runtime values of query variables.
214237
func isValidInputValue(value interface{}, ttype Input) (bool, []string) {
215-
if isNullish(value) {
238+
if _, ok := value.(nullValue); ok || isNullish(value) {
216239
if ttype, ok := ttype.(*NonNull); ok {
217240
if ttype.OfType.Name() != "" {
218241
return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())}
@@ -233,9 +256,14 @@ func isValidInputValue(value interface{}, ttype Input) (bool, []string) {
233256
messagesReduce := []string{}
234257
for i := 0; i < valType.Len(); i++ {
235258
val := valType.Index(i).Interface()
236-
_, messages := isValidInputValue(val, ttype.OfType)
237-
for idx, message := range messages {
238-
messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, idx+1, message))
259+
var messages []string
260+
if _, ok := val.(nullValue); ok {
261+
messages = []string{"Unexpected null value."}
262+
} else {
263+
_, messages = isValidInputValue(val, ttype.OfType)
264+
}
265+
for _, message := range messages {
266+
messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, i+1, message))
239267
}
240268
}
241269
return (len(messagesReduce) == 0), messagesReduce
@@ -352,6 +380,11 @@ func valueFromAST(valueAST ast.Value, ttype Input, variables map[string]interfac
352380
if valueAST == nil {
353381
return nil
354382
}
383+
384+
if valueAST.GetKind() == kinds.NullValue {
385+
return nullValue{}
386+
}
387+
355388
// precedence: value > type
356389
if valueAST, ok := valueAST.(*ast.Variable); ok {
357390
if valueAST.Name == nil || variables == nil {
@@ -398,9 +431,7 @@ func valueFromAST(valueAST ast.Value, ttype Input, variables map[string]interfac
398431
} else {
399432
value = field.DefaultValue
400433
}
401-
if !isNullish(value) {
402-
obj[name] = value
403-
}
434+
addValueOrNull(obj, name, value)
404435
}
405436
return obj
406437
case *Scalar:

0 commit comments

Comments
 (0)