Skip to content

Commit 1eea6f4

Browse files
author
Alan Colon
committed
Further flush out ability to auto-bind to functions and types, including recursion.
Fix code coverage, support additional functions. Add bind examples
1 parent 9441c49 commit 1eea6f4

File tree

5 files changed

+584
-19
lines changed

5 files changed

+584
-19
lines changed

bind.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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{}) *Field {
27+
val := reflect.ValueOf(bindTo)
28+
tipe := reflect.TypeOf(bindTo)
29+
if tipe.Kind() == reflect.Func {
30+
in := tipe.NumIn()
31+
out := tipe.NumOut()
32+
33+
var ctxIn *int
34+
var inputIn *int
35+
36+
var errOut *int
37+
var outputOut *int
38+
39+
queryArgs := FieldConfigArgument{}
40+
41+
if in > 2 {
42+
panic(fmt.Sprintf("Mismatch on number of inputs. Expected 0, 1, or 2. got %d.", tipe.NumIn()))
43+
}
44+
45+
if out > 2 {
46+
panic(fmt.Sprintf("Mismatch on number of outputs. Expected 0, 1, or 2, got %d.", tipe.NumOut()))
47+
}
48+
49+
// inTypes := make([]reflect.Type, in)
50+
// outTypes := make([]reflect.Type, out)
51+
52+
for i := 0; i < in; i++ {
53+
t := tipe.In(i)
54+
if t.Kind() == reflect.Ptr {
55+
t = t.Elem()
56+
}
57+
switch t {
58+
case ctxType:
59+
if ctxIn != nil {
60+
panic(fmt.Sprintf("Unexpected multiple *context.Context inputs."))
61+
}
62+
ctxIn = intP(i)
63+
default:
64+
if inputIn != nil {
65+
panic(fmt.Sprintf("Unexpected multiple inputs."))
66+
}
67+
inputType := tipe.In(i)
68+
if inputType.Kind() == reflect.Ptr {
69+
inputType = inputType.Elem()
70+
}
71+
inputFields := BindFields(reflect.New(inputType).Interface())
72+
for key, inputField := range inputFields {
73+
queryArgs[key] = &ArgumentConfig{
74+
Type: inputField.Type,
75+
}
76+
}
77+
78+
inputIn = intP(i)
79+
}
80+
}
81+
82+
for i := 0; i < out; i++ {
83+
t := tipe.Out(i)
84+
switch t.String() {
85+
case errType.String():
86+
if errOut != nil {
87+
panic(fmt.Sprintf("Unexpected multiple error outputs"))
88+
}
89+
errOut = intP(i)
90+
default:
91+
if outputOut != nil {
92+
panic(fmt.Sprintf("Unexpected multiple outputs"))
93+
}
94+
outputOut = intP(i)
95+
}
96+
}
97+
98+
resolve := func(p ResolveParams) (output interface{}, err error) {
99+
inputs := make([]reflect.Value, in)
100+
if ctxIn != nil {
101+
isPtr := tipe.In(*ctxIn).Kind() == reflect.Ptr
102+
if isPtr {
103+
inputs[*ctxIn] = reflect.ValueOf(&p.Context)
104+
} else {
105+
if p.Context == nil {
106+
inputs[*ctxIn] = reflect.New(ctxType).Elem()
107+
} else {
108+
inputs[*ctxIn] = reflect.ValueOf(p.Context).Elem()
109+
}
110+
}
111+
}
112+
if inputIn != nil {
113+
var inputType, baseType reflect.Type
114+
inputType = tipe.In(*inputIn)
115+
isPtr := tipe.In(*inputIn).Kind() == reflect.Ptr
116+
if isPtr {
117+
baseType = inputType.Elem()
118+
} else {
119+
baseType = inputType
120+
}
121+
input := reflect.New(baseType).Interface()
122+
j, err := json.Marshal(p.Args)
123+
if err == nil {
124+
err = json.Unmarshal(j, &input)
125+
}
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
if isPtr {
131+
inputs[*inputIn] = reflect.ValueOf(input)
132+
} else {
133+
if input == nil {
134+
inputs[*inputIn] = reflect.New(baseType).Elem()
135+
} else {
136+
inputs[*inputIn] = reflect.ValueOf(input).Elem()
137+
}
138+
}
139+
}
140+
141+
results := val.Call(inputs)
142+
if errOut != nil {
143+
val := results[*errOut].Interface()
144+
if val != nil {
145+
err = val.(error)
146+
}
147+
}
148+
if outputOut != nil {
149+
output = results[*outputOut].Interface()
150+
}
151+
return output, err
152+
}
153+
154+
var outputType Output
155+
if outputOut != nil {
156+
outputType = BindType(tipe.Out(*outputOut))
157+
}
158+
159+
field := &Field{
160+
Type: outputType,
161+
Resolve: resolve,
162+
Args: queryArgs,
163+
}
164+
165+
return field
166+
} else if tipe.Kind() == reflect.Struct {
167+
fieldType := BindType(reflect.TypeOf(bindTo))
168+
field := &Field{
169+
Type: fieldType,
170+
Resolve: func(p ResolveParams) (data interface{}, err error) {
171+
return bindTo, nil
172+
},
173+
}
174+
return field
175+
} else {
176+
return &Field{
177+
Type: getGraphType(tipe),
178+
Resolve: func(p ResolveParams) (data interface{}, err error) {
179+
return bindTo, nil
180+
},
181+
}
182+
}
183+
}
184+
185+
func intP(i int) *int {
186+
return &i
187+
}

0 commit comments

Comments
 (0)