Skip to content

Commit c23012d

Browse files
authored
Merge pull request #7 from dgraph-io/jatin/GraphQl-789
Fixes GRAPHQL-789 This PR allows coercing of single values in variables to list.
2 parents 11dafed + 8dcf919 commit c23012d

3 files changed

Lines changed: 64 additions & 41 deletions

File tree

validator/testdata/vars.graphql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Query {
1212
intArrayArg(i: [Int]): Boolean!
1313
stringArrayArg(i: [String]): Boolean!
1414
boolArrayArg(i: [Boolean]): Boolean!
15+
typeArrayArg(i: [CustomType]): Boolean!
1516
}
1617

1718
input InputType {
@@ -26,8 +27,12 @@ input Embedded {
2627
name: String!
2728
}
2829

30+
input CustomType {
31+
and: [Int!]
32+
}
33+
2934
enum Enum {
3035
A
3136
}
3237

33-
scalar Custom
38+
scalar Custom

validator/vars.go

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,16 @@ func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables m
5454
rv = rv.Elem()
5555
}
5656

57-
if err := validator.validateVarType(v.Type, rv); err != nil {
57+
rval, err := validator.validateVarType(v.Type, rv)
58+
if err != nil {
5859
return nil, err
5960
}
60-
61-
coercedVars[v.Variable] = val
61+
coercedVars[v.Variable] = rval.Interface()
6262
}
6363
}
6464

6565
validator.path = validator.path[0 : len(validator.path)-1]
6666
}
67-
6867
return coercedVars, nil
6968
}
7069

@@ -73,53 +72,52 @@ type varValidator struct {
7372
schema *ast.Schema
7473
}
7574

76-
func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerror.Error {
75+
func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) (reflect.Value, *gqlerror.Error) {
7776
currentPath := v.path
7877
resetPath := func() {
7978
v.path = currentPath
8079
}
8180
defer resetPath()
82-
8381
if typ.Elem != nil {
8482
if val.Kind() != reflect.Slice {
85-
return gqlerror.ErrorPathf(v.path, "must be an array")
83+
// GraphQL spec says that non-null values should be coerced to an array when possible.
84+
// Hence if the value is not a slice, we create a slice and add val to it.
85+
slc := reflect.MakeSlice(reflect.SliceOf(val.Type()), 0, 0)
86+
slc = reflect.Append(slc, val)
87+
val = slc
8688
}
87-
8889
for i := 0; i < val.Len(); i++ {
8990
resetPath()
9091
v.path = append(v.path, ast.PathIndex(i))
9192
field := val.Index(i)
92-
9393
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
9494
if typ.Elem.NonNull && field.IsNil() {
95-
return gqlerror.ErrorPathf(v.path, "cannot be null")
95+
return val, gqlerror.ErrorPathf(v.path, "cannot be null")
9696
}
9797
field = field.Elem()
9898
}
99-
100-
if err := v.validateVarType(typ.Elem, field); err != nil {
101-
return err
99+
_, err := v.validateVarType(typ.Elem, field)
100+
if err != nil {
101+
return val, err
102102
}
103103
}
104-
105-
return nil
104+
return val, nil
106105
}
107-
108106
def := v.schema.Types[typ.NamedType]
109107
if def == nil {
110108
panic(fmt.Errorf("missing def for %s", typ.NamedType))
111109
}
112110

113111
if !typ.NonNull && !val.IsValid() {
114112
// If the type is not null and we got a invalid value namely null/nil, then it's valid
115-
return nil
113+
return val, nil
116114
}
117115

118116
switch def.Kind {
119117
case ast.Enum:
120118
kind := val.Type().Kind()
121119
if kind != reflect.Int && kind != reflect.Int32 && kind != reflect.Int64 && kind != reflect.String {
122-
return gqlerror.ErrorPathf(v.path, "enums must be ints or strings")
120+
return val, gqlerror.ErrorPathf(v.path, "enums must be ints or strings")
123121
}
124122
isValidEnum := false
125123
for _, enumVal := range def.EnumValues {
@@ -128,42 +126,42 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr
128126
}
129127
}
130128
if !isValidEnum {
131-
return gqlerror.ErrorPathf(v.path, "%s is not a valid %s", val.String(), def.Name)
129+
return val, gqlerror.ErrorPathf(v.path, "%s is not a valid %s", val.String(), def.Name)
132130
}
133-
return nil
131+
return val, nil
134132
case ast.Scalar:
135133
kind := val.Type().Kind()
136134
switch typ.NamedType {
137135
case "Int":
138136
if kind == reflect.String || kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 {
139-
return nil
137+
return val, nil
140138
}
141139
case "Float":
142140
if kind == reflect.String || kind == reflect.Float32 || kind == reflect.Float64 || kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 {
143-
return nil
141+
return val, nil
144142
}
145143
case "String":
146144
if kind == reflect.String {
147-
return nil
145+
return val, nil
148146
}
149147

150148
case "Boolean":
151149
if kind == reflect.Bool {
152-
return nil
150+
return val, nil
153151
}
154152

155153
case "ID":
156154
if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.String {
157-
return nil
155+
return val, nil
158156
}
159157
default:
160158
// assume custom scalars are ok
161-
return nil
159+
return val, nil
162160
}
163-
return gqlerror.ErrorPathf(v.path, "cannot use %s as %s", kind.String(), typ.NamedType)
161+
return val, gqlerror.ErrorPathf(v.path, "cannot use %s as %s", kind.String(), typ.NamedType)
164162
case ast.InputObject:
165163
if val.Kind() != reflect.Map {
166-
return gqlerror.ErrorPathf(v.path, "must be a %s", def.Name)
164+
return val, gqlerror.ErrorPathf(v.path, "must be a %s", def.Name)
167165
}
168166

169167
// check for unknown fields
@@ -174,7 +172,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr
174172
v.path = append(v.path, ast.PathName(name.String()))
175173

176174
if fieldDef == nil {
177-
return gqlerror.ErrorPathf(v.path, "unknown field")
175+
return val, gqlerror.ErrorPathf(v.path, "unknown field")
178176
}
179177
}
180178

@@ -192,30 +190,29 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr
192190
continue
193191
}
194192
}
195-
return gqlerror.ErrorPathf(v.path, "must be defined")
193+
return val, gqlerror.ErrorPathf(v.path, "must be defined")
196194
}
197195
continue
198196
}
199197

200198
if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
201199
if fieldDef.Type.NonNull && field.IsNil() {
202-
return gqlerror.ErrorPathf(v.path, "cannot be null")
200+
return val, gqlerror.ErrorPathf(v.path, "cannot be null")
203201
}
204202
//allow null object field and skip it
205203
if !fieldDef.Type.NonNull && field.IsNil() {
206204
continue
207205
}
208206
field = field.Elem()
209207
}
210-
211-
err := v.validateVarType(fieldDef.Type, field)
208+
cval, err := v.validateVarType(fieldDef.Type, field)
212209
if err != nil {
213-
return err
210+
return val, err
214211
}
212+
val.SetMapIndex(reflect.ValueOf(fieldDef.Name), cval)
215213
}
216214
default:
217215
panic(fmt.Errorf("unsupported type %s", def.Kind))
218216
}
219-
220-
return nil
217+
return val, nil
221218
}

validator/vars_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,33 @@ func TestValidateVars(t *testing.T) {
151151
})
152152

153153
t.Run("array", func(t *testing.T) {
154-
t.Run("non array", func(t *testing.T) {
154+
t.Run("non-null object value should be coerced to an array", func(t *testing.T) {
155155
q := gqlparser.MustLoadQuery(schema, `query foo($var: [InputType!]) { arrayArg(i: $var) }`)
156-
_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), map[string]interface{}{
157-
"var": "hello",
156+
vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), map[string]interface{}{
157+
"var": map[string]interface{}{"name": "hello"},
158+
})
159+
require.Nil(t, gerr)
160+
require.EqualValues(t, []map[string]interface{}{{"name": "hello"}}, vars["var"])
161+
})
162+
163+
t.Run("non-null int value should be coerced to an array", func(t *testing.T) {
164+
q := gqlparser.MustLoadQuery(schema, `query foo($var: [Int!]) { intArrayArg(i: $var) }`)
165+
vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), map[string]interface{}{
166+
"var": 5,
158167
})
159-
require.EqualError(t, gerr, "input: variable.var must be an array")
168+
require.Nil(t, gerr)
169+
expected := []int{5}
170+
require.EqualValues(t, expected, vars["var"])
171+
})
172+
173+
t.Run("non-null int deep value should be coerced to an array", func(t *testing.T) {
174+
q := gqlparser.MustLoadQuery(schema, `query foo($var: [CustomType]) { typeArrayArg(i: $var) }`)
175+
vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), map[string]interface{}{
176+
"var": []map[string]interface{}{{"and": 5}},
177+
})
178+
require.Nil(t, gerr)
179+
expected := []map[string]interface{}{{"and": []int{5}}}
180+
require.EqualValues(t, expected, vars["var"])
160181
})
161182

162183
t.Run("defaults", func(t *testing.T) {

0 commit comments

Comments
 (0)