Skip to content

Commit c6692af

Browse files
committed
Fix support for null literals
1 parent 99cf6bd commit c6692af

File tree

6 files changed

+353
-38
lines changed

6 files changed

+353
-38
lines changed

definition.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ func GetNullable(ttype Type) Nullable {
158158
return ttype
159159
}
160160

161-
// NullValue to be able to detect if a value is set to null or if it is omitted
162-
type NullValue struct {}
163-
164161
// Named interface for types that do not include modifiers like List or NonNull.
165162
type Named interface {
166163
String() string

graphql_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,300 @@ func TestEmptyStringIsNotNull(t *testing.T) {
268268
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
269269
}
270270
}
271+
272+
func TestNullLiteralArguments(t *testing.T) {
273+
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
274+
arg, ok := p.Args["arg"]
275+
if !ok || arg != nil {
276+
t.Errorf("expected null for input arg, got %#v", arg)
277+
}
278+
return "yay", nil
279+
}
280+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
281+
Query: graphql.NewObject(graphql.ObjectConfig{
282+
Name: "Query",
283+
Fields: graphql.Fields{
284+
"checkNullStringArg": &graphql.Field{
285+
Type: graphql.String,
286+
Args: graphql.FieldConfigArgument{
287+
"arg": &graphql.ArgumentConfig{Type: graphql.String},
288+
},
289+
Resolve: checkForNull,
290+
},
291+
"checkNullIntArg": &graphql.Field{
292+
Type: graphql.String,
293+
Args: graphql.FieldConfigArgument{
294+
"arg": &graphql.ArgumentConfig{Type: graphql.Int},
295+
},
296+
Resolve: checkForNull,
297+
},
298+
"checkNullBooleanArg": &graphql.Field{
299+
Type: graphql.String,
300+
Args: graphql.FieldConfigArgument{
301+
"arg": &graphql.ArgumentConfig{Type: graphql.Boolean},
302+
},
303+
Resolve: checkForNull,
304+
},
305+
"checkNullListArg": &graphql.Field{
306+
Type: graphql.String,
307+
Args: graphql.FieldConfigArgument{
308+
"arg": &graphql.ArgumentConfig{Type: graphql.NewList(graphql.String)},
309+
},
310+
Resolve: checkForNull,
311+
},
312+
"checkNullInputObjectArg": &graphql.Field{
313+
Type: graphql.String,
314+
Args: graphql.FieldConfigArgument{
315+
"arg": &graphql.ArgumentConfig{Type: graphql.NewInputObject(
316+
graphql.InputObjectConfig{
317+
Name: "InputType",
318+
Fields: graphql.InputObjectConfigFieldMap{
319+
"field1": {Type: graphql.String},
320+
"field2": {Type: graphql.Int},
321+
},
322+
})},
323+
},
324+
Resolve: checkForNull,
325+
},
326+
},
327+
}),
328+
})
329+
if err != nil {
330+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
331+
}
332+
query := `{ checkNullStringArg(arg:null) checkNullIntArg(arg:null) checkNullBooleanArg(arg:null) checkNullListArg(arg:null) checkNullInputObjectArg(arg:null) }`
333+
334+
result := graphql.Do(graphql.Params{
335+
Schema: schema,
336+
RequestString: query,
337+
})
338+
if len(result.Errors) > 0 {
339+
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
340+
}
341+
expected := map[string]interface{}{
342+
"checkNullStringArg": "yay", "checkNullIntArg": "yay",
343+
"checkNullBooleanArg": "yay", "checkNullListArg": "yay",
344+
"checkNullInputObjectArg": "yay"}
345+
if !reflect.DeepEqual(result.Data, expected) {
346+
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
347+
}
348+
}
349+
350+
func TestNullLiteralDefaultVariableValue(t *testing.T) {
351+
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
352+
arg, ok := p.Args["arg"]
353+
if !ok || arg != nil {
354+
t.Errorf("expected null for input arg, got %#v", arg)
355+
}
356+
return "yay", nil
357+
}
358+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
359+
Query: graphql.NewObject(graphql.ObjectConfig{
360+
Name: "Query",
361+
Fields: graphql.Fields{
362+
"checkNullStringArg": &graphql.Field{
363+
Type: graphql.String,
364+
Args: graphql.FieldConfigArgument{
365+
"arg": &graphql.ArgumentConfig{Type: graphql.String},
366+
},
367+
Resolve: checkForNull,
368+
},
369+
},
370+
}),
371+
})
372+
if err != nil {
373+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
374+
}
375+
query := `query Test($value: String = null) { checkNullStringArg(arg: $value) }`
376+
377+
result := graphql.Do(graphql.Params{
378+
Schema: schema,
379+
RequestString: query,
380+
VariableValues: map[string]interface{}{"value2": nil},
381+
})
382+
if len(result.Errors) > 0 {
383+
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
384+
}
385+
expected := map[string]interface{}{ "checkNullStringArg": "yay", }
386+
if !reflect.DeepEqual(result.Data, expected) {
387+
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
388+
}
389+
}
390+
391+
func TestNullLiteralVariables(t *testing.T) {
392+
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
393+
arg, ok := p.Args["arg"]
394+
if !ok || arg != nil {
395+
t.Errorf("expected null for input arg, got %#v", arg)
396+
}
397+
return "yay", nil
398+
}
399+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
400+
Query: graphql.NewObject(graphql.ObjectConfig{
401+
Name: "Query",
402+
Fields: graphql.Fields{
403+
"checkNullStringArg": &graphql.Field{
404+
Type: graphql.String,
405+
Args: graphql.FieldConfigArgument{
406+
"arg": &graphql.ArgumentConfig{Type: graphql.String},
407+
},
408+
Resolve: checkForNull,
409+
},
410+
},
411+
}),
412+
})
413+
if err != nil {
414+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
415+
}
416+
query := `query Test($value: String) { checkNullStringArg(arg: $value) }`
417+
418+
result := graphql.Do(graphql.Params{
419+
Schema: schema,
420+
RequestString: query,
421+
VariableValues: map[string]interface{}{"value": nil},
422+
})
423+
if len(result.Errors) > 0 {
424+
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
425+
}
426+
expected := map[string]interface{}{ "checkNullStringArg": "yay", }
427+
if !reflect.DeepEqual(result.Data, expected) {
428+
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
429+
}
430+
}
431+
432+
func TestErrorNullLiteralForNotNullArgument(t *testing.T) {
433+
checkNotCalled := func(p graphql.ResolveParams) (interface{}, error) {
434+
t.Error("shouldn't have been called")
435+
return nil, nil
436+
}
437+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
438+
Query: graphql.NewObject(graphql.ObjectConfig{
439+
Name: "Query",
440+
Fields: graphql.Fields{
441+
"checkNotNullArg": &graphql.Field{
442+
Type: graphql.String,
443+
Args: graphql.FieldConfigArgument{
444+
"arg": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.String) },
445+
},
446+
Resolve: checkNotCalled,
447+
},
448+
},
449+
}),
450+
})
451+
if err != nil {
452+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
453+
}
454+
query := `{ checkNotNullArg(arg:null) }`
455+
456+
result := graphql.Do(graphql.Params{
457+
Schema: schema,
458+
RequestString: query,
459+
})
460+
461+
if len(result.Errors) == 0 {
462+
t.Fatalf("expected errors, got: %v", result)
463+
}
464+
465+
expectedMessage := `Argument "arg" has invalid value <nil>.
466+
Expected "String!", found null.`;
467+
468+
if result.Errors[0].Message != expectedMessage {
469+
t.Fatalf("unexpected error.\nexpected:\n%s\ngot:\n%s\n", expectedMessage, result.Errors[0].Message)
470+
}
471+
}
472+
473+
func TestNullInputObjectFields(t *testing.T) {
474+
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
475+
arg := p.Args["arg"]
476+
expectedValue := map[string]interface{}{ "field1": nil, "field2": nil, "field3": nil, "field4" : "abc", "field5": 42, "field6": true}
477+
if value, ok := arg.(map[string]interface{}); !ok {
478+
t.Errorf("expected map[string]interface{} for input arg, got %#v", arg)
479+
} else if !reflect.DeepEqual(expectedValue, value) {
480+
t.Errorf("unexpected input object, diff: %v", testutil.Diff(expectedValue, value))
481+
}
482+
return "yay", nil
483+
}
484+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
485+
Query: graphql.NewObject(graphql.ObjectConfig{
486+
Name: "Query",
487+
Fields: graphql.Fields{
488+
"checkNullInputObjectFields": &graphql.Field{
489+
Type: graphql.String,
490+
Args: graphql.FieldConfigArgument{
491+
"arg": &graphql.ArgumentConfig{Type: graphql.NewInputObject(
492+
graphql.InputObjectConfig{
493+
Name: "InputType",
494+
Fields: graphql.InputObjectConfigFieldMap{
495+
"field1": {Type: graphql.String},
496+
"field2": {Type: graphql.Int},
497+
"field3": {Type: graphql.Boolean},
498+
"field4": {Type: graphql.String},
499+
"field5": {Type: graphql.Int},
500+
"field6": {Type: graphql.Boolean},
501+
},
502+
})},
503+
},
504+
Resolve: checkForNull,
505+
},
506+
},
507+
}),
508+
})
509+
if err != nil {
510+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
511+
}
512+
query := `{ checkNullInputObjectFields(arg: {field1: null, field2: null, field3: null, field4: "abc", field5: 42, field6: true }) }`
513+
514+
result := graphql.Do(graphql.Params{
515+
Schema: schema,
516+
RequestString: query,
517+
})
518+
if len(result.Errors) > 0 {
519+
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
520+
}
521+
expected := map[string]interface{}{ "checkNullInputObjectFields": "yay" }
522+
if !reflect.DeepEqual(result.Data, expected) {
523+
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
524+
}
525+
}
526+
527+
func TestErrorNullInList(t *testing.T) {
528+
checkNotCalled := func(p graphql.ResolveParams) (interface{}, error) {
529+
t.Error("shouldn't have been called")
530+
return nil, nil
531+
}
532+
schema, err := graphql.NewSchema(graphql.SchemaConfig{
533+
Query: graphql.NewObject(graphql.ObjectConfig{
534+
Name: "Query",
535+
Fields: graphql.Fields{
536+
"checkNotNullInListArg": &graphql.Field{
537+
Type: graphql.String,
538+
Args: graphql.FieldConfigArgument{
539+
"arg": &graphql.ArgumentConfig{Type: graphql.NewList(graphql.String) },
540+
},
541+
Resolve: checkNotCalled,
542+
},
543+
},
544+
}),
545+
})
546+
if err != nil {
547+
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
548+
}
549+
query := `{ checkNotNullInListArg(arg: [null, null]) }`
550+
551+
result := graphql.Do(graphql.Params{
552+
Schema: schema,
553+
RequestString: query,
554+
})
555+
556+
if len(result.Errors) == 0 {
557+
t.Fatalf("expected errors, got: %v", result)
558+
}
559+
560+
expectedMessage := `Argument "arg" has invalid value [<nil>, <nil>].
561+
In element #1: Unexpected null literal.
562+
In element #2: Unexpected null literal.`
563+
564+
if result.Errors[0].Message != expectedMessage {
565+
t.Fatalf("unexpected error.\nexpected:\n%s\ngot:\n%s\n", expectedMessage, result.Errors[0].Message)
566+
}
567+
}

rules.go

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

1733-
// This function only tests literals, and assumes variables will provide
1734-
// values of the correct type.
17351733
if valueAST.GetKind() == kinds.NullValue {
17361734
return true, nil
17371735
}
@@ -1748,7 +1746,7 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
17481746
if e := ttype.Error(); e != nil {
17491747
return false, []string{e.Error()}
17501748
}
1751-
if valueAST == nil {
1749+
if valueAST == nil || valueAST.GetKind() == kinds.NullValue {
17521750
if ttype.OfType.Name() != "" {
17531751
return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())}
17541752
}
@@ -1762,7 +1760,12 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
17621760
if valueAST, ok := valueAST.(*ast.ListValue); ok {
17631761
messagesReduce := []string{}
17641762
for idx, value := range valueAST.Values {
1765-
_, messages := isValidLiteralValue(itemType, value)
1763+
var messages []string
1764+
if value.GetKind() == kinds.NullValue {
1765+
messages = []string{"Unexpected null literal."}
1766+
} else {
1767+
_, messages = isValidLiteralValue(itemType, value)
1768+
}
17661769
for _, message := range messages {
17671770
messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, idx+1, message))
17681771
}

scalars.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,6 @@ var Int = NewScalar(ScalarConfig{
162162
if intValue, err := strconv.Atoi(valueAST.Value); err == nil {
163163
return intValue
164164
}
165-
case *ast.NullValue:
166-
return NullValue{}
167165
}
168166
return nil
169167
},
@@ -301,8 +299,6 @@ var Float = NewScalar(ScalarConfig{
301299
if floatValue, err := strconv.ParseFloat(valueAST.Value, 64); err == nil {
302300
return floatValue
303301
}
304-
case *ast.NullValue:
305-
return NullValue{}
306302
}
307303
return nil
308304
},
@@ -330,8 +326,6 @@ var String = NewScalar(ScalarConfig{
330326
switch valueAST := valueAST.(type) {
331327
case *ast.StringValue:
332328
return valueAST.Value
333-
case *ast.NullValue:
334-
return NullValue{}
335329
}
336330

337331
return nil
@@ -492,8 +486,6 @@ var Boolean = NewScalar(ScalarConfig{
492486
switch valueAST := valueAST.(type) {
493487
case *ast.BooleanValue:
494488
return valueAST.Value
495-
case *ast.NullValue:
496-
return NullValue{}
497489
}
498490
return nil
499491
},
@@ -515,8 +507,6 @@ var ID = NewScalar(ScalarConfig{
515507
return valueAST.Value
516508
case *ast.StringValue:
517509
return valueAST.Value
518-
case *ast.NullValue:
519-
return NullValue{}
520510
}
521511
return nil
522512
},
@@ -576,8 +566,6 @@ var DateTime = NewScalar(ScalarConfig{
576566
case *ast.StringValue:
577567
return unserializeDateTime(valueAST.Value)
578568
return valueAST.Value
579-
case *ast.NullValue:
580-
return NullValue{}
581569
}
582570
return nil
583571
},

0 commit comments

Comments
 (0)