Skip to content

Commit 9680004

Browse files
Alan Colonalancnet
Alan Colon
authored andcommitted
Further flush out ability to auto-bind to functions and types, including recursion.
Fix code coverage, support additional functions. Add bind examples Avoid errors where value IsZero Add type tag, nested resolvers on Bind Extend type Don't call .Type() on zero values
1 parent 4ebf270 commit 9680004

File tree

6 files changed

+724
-27
lines changed

6 files changed

+724
-27
lines changed

bind.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package graphql
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"reflect"
8+
)
9+
10+
var ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
11+
var errType = reflect.TypeOf((*error)(nil)).Elem()
12+
13+
/*
14+
Bind will create a Field around a function formatted a certain way, or any value.
15+
16+
The input parameters can be, in any order,
17+
- context.Context, or *context.Context (optional)
18+
- An input struct, or pointer (optional)
19+
20+
The output parameters can be, in any order,
21+
- A primitive, an output struct, or pointer (required for use in schema)
22+
- error (optional)
23+
24+
Input or output types provided will be automatically bound using BindType.
25+
*/
26+
func Bind(bindTo interface{}, additionalFields ...Fields) *Field {
27+
combinedAdditionalFields := MergeFields(additionalFields...)
28+
val := reflect.ValueOf(bindTo)
29+
tipe := reflect.TypeOf(bindTo)
30+
if tipe.Kind() == reflect.Func {
31+
in := tipe.NumIn()
32+
out := tipe.NumOut()
33+
34+
var ctxIn *int
35+
var inputIn *int
36+
37+
var errOut *int
38+
var outputOut *int
39+
40+
queryArgs := FieldConfigArgument{}
41+
42+
if in > 2 {
43+
panic(fmt.Sprintf("Mismatch on number of inputs. Expected 0, 1, or 2. got %d.", tipe.NumIn()))
44+
}
45+
46+
if out > 2 {
47+
panic(fmt.Sprintf("Mismatch on number of outputs. Expected 0, 1, or 2, got %d.", tipe.NumOut()))
48+
}
49+
50+
// inTypes := make([]reflect.Type, in)
51+
// outTypes := make([]reflect.Type, out)
52+
53+
for i := 0; i < in; i++ {
54+
t := tipe.In(i)
55+
if t.Kind() == reflect.Ptr {
56+
t = t.Elem()
57+
}
58+
switch t {
59+
case ctxType:
60+
if ctxIn != nil {
61+
panic(fmt.Sprintf("Unexpected multiple *context.Context inputs."))
62+
}
63+
ctxIn = intP(i)
64+
default:
65+
if inputIn != nil {
66+
panic(fmt.Sprintf("Unexpected multiple inputs."))
67+
}
68+
inputType := tipe.In(i)
69+
if inputType.Kind() == reflect.Ptr {
70+
inputType = inputType.Elem()
71+
}
72+
inputFields := BindFields(reflect.New(inputType).Interface())
73+
for key, inputField := range inputFields {
74+
queryArgs[key] = &ArgumentConfig{
75+
Type: inputField.Type,
76+
}
77+
}
78+
79+
inputIn = intP(i)
80+
}
81+
}
82+
83+
for i := 0; i < out; i++ {
84+
t := tipe.Out(i)
85+
switch t.String() {
86+
case errType.String():
87+
if errOut != nil {
88+
panic(fmt.Sprintf("Unexpected multiple error outputs"))
89+
}
90+
errOut = intP(i)
91+
default:
92+
if outputOut != nil {
93+
panic(fmt.Sprintf("Unexpected multiple outputs"))
94+
}
95+
outputOut = intP(i)
96+
}
97+
}
98+
99+
resolve := func(p ResolveParams) (output interface{}, err error) {
100+
inputs := make([]reflect.Value, in)
101+
if ctxIn != nil {
102+
isPtr := tipe.In(*ctxIn).Kind() == reflect.Ptr
103+
if isPtr {
104+
if p.Context == nil {
105+
inputs[*ctxIn] = reflect.New(ctxType)
106+
} else {
107+
inputs[*ctxIn] = reflect.ValueOf(&p.Context)
108+
}
109+
} else {
110+
if p.Context == nil {
111+
inputs[*ctxIn] = reflect.New(ctxType).Elem()
112+
} else {
113+
inputs[*ctxIn] = reflect.ValueOf(p.Context).Convert(ctxType).Elem()
114+
}
115+
}
116+
}
117+
if inputIn != nil {
118+
var inputType, inputBaseType, sourceType, sourceBaseType reflect.Type
119+
sourceVal := reflect.ValueOf(p.Source)
120+
sourceExists := !sourceVal.IsZero()
121+
if sourceExists {
122+
sourceType = sourceVal.Type()
123+
if sourceType.Kind() == reflect.Ptr {
124+
sourceBaseType = sourceType.Elem()
125+
} else {
126+
sourceBaseType = sourceType
127+
}
128+
}
129+
inputType = tipe.In(*inputIn)
130+
isPtr := tipe.In(*inputIn).Kind() == reflect.Ptr
131+
if isPtr {
132+
inputBaseType = inputType.Elem()
133+
} else {
134+
inputBaseType = inputType
135+
}
136+
var input interface{}
137+
if sourceExists && sourceBaseType.AssignableTo(inputBaseType) {
138+
input = sourceVal.Interface()
139+
} else {
140+
input = reflect.New(inputBaseType).Interface()
141+
j, err := json.Marshal(p.Args)
142+
if err == nil {
143+
err = json.Unmarshal(j, &input)
144+
}
145+
if err != nil {
146+
return nil, err
147+
}
148+
}
149+
150+
inputs[*inputIn], err = convertValue(reflect.ValueOf(input), inputType)
151+
if err != nil {
152+
return nil, err
153+
}
154+
}
155+
results := val.Call(inputs)
156+
if errOut != nil {
157+
val := results[*errOut].Interface()
158+
if val != nil {
159+
err = val.(error)
160+
}
161+
if err != nil {
162+
return output, err
163+
}
164+
}
165+
if outputOut != nil {
166+
var val reflect.Value
167+
val, err = convertValue(results[*outputOut], tipe.Out(*outputOut))
168+
if err != nil {
169+
return nil, err
170+
}
171+
if !val.IsZero() {
172+
output = val.Interface()
173+
}
174+
}
175+
return output, err
176+
}
177+
178+
var outputType Output
179+
if outputOut != nil {
180+
outputType = BindType(tipe.Out(*outputOut))
181+
extendType(outputType, combinedAdditionalFields)
182+
}
183+
184+
field := &Field{
185+
Type: outputType,
186+
Resolve: resolve,
187+
Args: queryArgs,
188+
}
189+
190+
return field
191+
} else if tipe.Kind() == reflect.Struct {
192+
fieldType := BindType(reflect.TypeOf(bindTo))
193+
extendType(fieldType, combinedAdditionalFields)
194+
field := &Field{
195+
Type: fieldType,
196+
Resolve: func(p ResolveParams) (data interface{}, err error) {
197+
return bindTo, nil
198+
},
199+
}
200+
return field
201+
} else {
202+
if len(additionalFields) > 0 {
203+
panic("Cannot add field resolvers to a scalar type.")
204+
}
205+
return &Field{
206+
Type: getGraphType(tipe),
207+
Resolve: func(p ResolveParams) (data interface{}, err error) {
208+
return bindTo, nil
209+
},
210+
}
211+
}
212+
}
213+
214+
func extendType(t Type, fields Fields) {
215+
switch t.(type) {
216+
case *Object:
217+
object := t.(*Object)
218+
for fieldName, fieldConfig := range fields {
219+
object.AddFieldConfig(fieldName, fieldConfig)
220+
}
221+
return
222+
case *List:
223+
list := t.(*List)
224+
extendType(list.OfType, fields)
225+
return
226+
}
227+
}
228+
229+
func convertValue(value reflect.Value, targetType reflect.Type) (ret reflect.Value, err error) {
230+
if !value.IsValid() || value.IsZero() {
231+
return reflect.Zero(targetType), nil
232+
}
233+
if value.Type().Kind() == reflect.Ptr {
234+
if targetType.Kind() == reflect.Ptr {
235+
return value, nil
236+
} else {
237+
return value.Elem(), nil
238+
}
239+
} else {
240+
if targetType.Kind() == reflect.Ptr {
241+
// Will throw an informative error
242+
return value.Convert(targetType), nil
243+
} else {
244+
return value, nil
245+
}
246+
}
247+
}
248+
249+
func intP(i int) *int {
250+
return &i
251+
}

0 commit comments

Comments
 (0)