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

Fix support for null literals #536

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
297 changes: 297 additions & 0 deletions graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,300 @@ func TestEmptyStringIsNotNull(t *testing.T) {
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
}
}

func TestNullLiteralArguments(t *testing.T) {
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
arg, ok := p.Args["arg"]
if !ok || arg != nil {
t.Errorf("expected null for input arg, got %#v", arg)
}
return "yay", nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNullStringArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.String},
},
Resolve: checkForNull,
},
"checkNullIntArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.Int},
},
Resolve: checkForNull,
},
"checkNullBooleanArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.Boolean},
},
Resolve: checkForNull,
},
"checkNullListArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.NewList(graphql.String)},
},
Resolve: checkForNull,
},
"checkNullInputObjectArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.NewInputObject(
graphql.InputObjectConfig{
Name: "InputType",
Fields: graphql.InputObjectConfigFieldMap{
"field1": {Type: graphql.String},
"field2": {Type: graphql.Int},
},
})},
},
Resolve: checkForNull,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `{ checkNullStringArg(arg:null) checkNullIntArg(arg:null) checkNullBooleanArg(arg:null) checkNullListArg(arg:null) checkNullInputObjectArg(arg:null) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
}
expected := map[string]interface{}{
"checkNullStringArg": "yay", "checkNullIntArg": "yay",
"checkNullBooleanArg": "yay", "checkNullListArg": "yay",
"checkNullInputObjectArg": "yay"}
if !reflect.DeepEqual(result.Data, expected) {
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
}
}

func TestNullLiteralDefaultVariableValue(t *testing.T) {
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
arg, ok := p.Args["arg"]
if !ok || arg != nil {
t.Errorf("expected null for input arg, got %#v", arg)
}
return "yay", nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNullStringArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.String},
},
Resolve: checkForNull,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `query Test($value: String = null) { checkNullStringArg(arg: $value) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: map[string]interface{}{"value2": nil},
})
if len(result.Errors) > 0 {
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
}
expected := map[string]interface{}{ "checkNullStringArg": "yay", }
if !reflect.DeepEqual(result.Data, expected) {
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
}
}

func TestNullLiteralVariables(t *testing.T) {
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
arg, ok := p.Args["arg"]
if !ok || arg != nil {
t.Errorf("expected null for input arg, got %#v", arg)
}
return "yay", nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNullStringArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.String},
},
Resolve: checkForNull,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `query Test($value: String) { checkNullStringArg(arg: $value) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: map[string]interface{}{"value": nil},
})
if len(result.Errors) > 0 {
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
}
expected := map[string]interface{}{ "checkNullStringArg": "yay", }
if !reflect.DeepEqual(result.Data, expected) {
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
}
}

func TestErrorNullLiteralForNotNullArgument(t *testing.T) {
checkNotCalled := func(p graphql.ResolveParams) (interface{}, error) {
t.Error("shouldn't have been called")
return nil, nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNotNullArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.String) },
},
Resolve: checkNotCalled,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `{ checkNotNullArg(arg:null) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})

if len(result.Errors) == 0 {
t.Fatalf("expected errors, got: %v", result)
}

expectedMessage := `Argument "arg" has invalid value <nil>.
Expected "String!", found null.`;

if result.Errors[0].Message != expectedMessage {
t.Fatalf("unexpected error.\nexpected:\n%s\ngot:\n%s\n", expectedMessage, result.Errors[0].Message)
}
}

func TestNullInputObjectFields(t *testing.T) {
checkForNull := func(p graphql.ResolveParams) (interface{}, error) {
arg := p.Args["arg"]
expectedValue := map[string]interface{}{ "field1": nil, "field2": nil, "field3": nil, "field4" : "abc", "field5": 42, "field6": true}
if value, ok := arg.(map[string]interface{}); !ok {
t.Errorf("expected map[string]interface{} for input arg, got %#v", arg)
} else if !reflect.DeepEqual(expectedValue, value) {
t.Errorf("unexpected input object, diff: %v", testutil.Diff(expectedValue, value))
}
return "yay", nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNullInputObjectFields": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.NewInputObject(
graphql.InputObjectConfig{
Name: "InputType",
Fields: graphql.InputObjectConfigFieldMap{
"field1": {Type: graphql.String},
"field2": {Type: graphql.Int},
"field3": {Type: graphql.Boolean},
"field4": {Type: graphql.String},
"field5": {Type: graphql.Int},
"field6": {Type: graphql.Boolean},
},
})},
},
Resolve: checkForNull,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `{ checkNullInputObjectFields(arg: {field1: null, field2: null, field3: null, field4: "abc", field5: 42, field6: true }) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
}
expected := map[string]interface{}{ "checkNullInputObjectFields": "yay" }
if !reflect.DeepEqual(result.Data, expected) {
t.Errorf("wrong result, query: %v, graphql result diff: %v", query, testutil.Diff(expected, result))
}
}

func TestErrorNullInList(t *testing.T) {
checkNotCalled := func(p graphql.ResolveParams) (interface{}, error) {
t.Error("shouldn't have been called")
return nil, nil
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"checkNotNullInListArg": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
"arg": &graphql.ArgumentConfig{Type: graphql.NewList(graphql.String) },
},
Resolve: checkNotCalled,
},
},
}),
})
if err != nil {
t.Fatalf("wrong result, unexpected errors: %v", err.Error())
}
query := `{ checkNotNullInListArg(arg: [null, null]) }`

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})

if len(result.Errors) == 0 {
t.Fatalf("expected errors, got: %v", result)
}

expectedMessage := `Argument "arg" has invalid value [<nil>, <nil>].
In element #1: Unexpected null literal.
In element #2: Unexpected null literal.`

if result.Errors[0].Message != expectedMessage {
t.Fatalf("unexpected error.\nexpected:\n%s\ngot:\n%s\n", expectedMessage, result.Errors[0].Message)
}
}
1 change: 1 addition & 0 deletions language/ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var _ Node = (*IntValue)(nil)
var _ Node = (*FloatValue)(nil)
var _ Node = (*StringValue)(nil)
var _ Node = (*BooleanValue)(nil)
var _ Node = (*NullValue)(nil)
var _ Node = (*EnumValue)(nil)
var _ Node = (*ListValue)(nil)
var _ Node = (*ObjectValue)(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 @@ -16,6 +16,7 @@ var _ Value = (*IntValue)(nil)
var _ Value = (*FloatValue)(nil)
var _ Value = (*StringValue)(nil)
var _ Value = (*BooleanValue)(nil)
var _ Value = (*NullValue)(nil)
var _ Value = (*EnumValue)(nil)
var _ Value = (*ListValue)(nil)
var _ Value = (*ObjectValue)(nil)
Expand Down Expand Up @@ -172,6 +173,34 @@ func (v *BooleanValue) GetValue() interface{} {
return v.Value
}

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

func NewNullValue(v *NullValue) *NullValue {

return &NullValue{
Kind: kinds.NullValue,
Loc: v.Loc,
Value: v.Value,
}
}

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

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

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

// EnumValue implements Node, Value
type EnumValue struct {
Kind string
Expand Down
1 change: 1 addition & 0 deletions language/kinds/kinds.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
FloatValue = "FloatValue"
StringValue = "StringValue"
BooleanValue = "BooleanValue"
NullValue = "NullValue"
EnumValue = "EnumValue"
ListValue = "ListValue"
ObjectValue = "ObjectValue"
Expand Down
10 changes: 9 additions & 1 deletion language/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,15 @@ 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{
Value: nil,
Loc: loc(parser, token.Start),
}), nil
} else {
if err := advance(parser); err != nil {
return nil, err
}
Expand Down
Loading