Skip to content

Commit 3e619b6

Browse files
authored
Merge pull request #197 from ahmadmuzakki29/util
Add BindFields function tool
2 parents fe52096 + 6611f08 commit 3e619b6

File tree

2 files changed

+337
-0
lines changed

2 files changed

+337
-0
lines changed

util.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package graphql
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
const TAG = "json"
10+
11+
// can't take recursive slice type
12+
// e.g
13+
// type Person struct{
14+
// Friends []Person
15+
// }
16+
// it will throw panic stack-overflow
17+
func BindFields(obj interface{}) Fields {
18+
v := reflect.ValueOf(obj)
19+
fields := make(map[string]*Field)
20+
21+
for i := 0; i < v.NumField(); i++ {
22+
typeField := v.Type().Field(i)
23+
24+
tag := typeField.Tag.Get(TAG)
25+
if tag == "-" {
26+
continue
27+
}
28+
var graphType Output
29+
if typeField.Type.Kind() == reflect.Struct {
30+
31+
structFields := BindFields(v.Field(i).Interface())
32+
if tag == "" {
33+
fields = appendFields(fields, structFields)
34+
continue
35+
} else {
36+
graphType = NewObject(ObjectConfig{
37+
Name: tag,
38+
Fields: structFields,
39+
})
40+
}
41+
}
42+
43+
if tag == "" {
44+
continue
45+
}
46+
47+
if graphType == nil {
48+
graphType = getGraphType(typeField.Type)
49+
}
50+
fields[tag] = &Field{
51+
Type: graphType,
52+
Resolve: func(p ResolveParams) (interface{}, error) {
53+
return extractValue(tag, p.Source), nil
54+
},
55+
}
56+
}
57+
return fields
58+
}
59+
60+
func getGraphType(tipe reflect.Type) Output {
61+
kind := tipe.Kind()
62+
switch kind {
63+
case reflect.String:
64+
return String
65+
case reflect.Int:
66+
return Int
67+
case reflect.Float32:
68+
case reflect.Float64:
69+
return Float
70+
case reflect.Bool:
71+
return Boolean
72+
case reflect.Slice:
73+
return getGraphList(tipe)
74+
}
75+
return String
76+
}
77+
78+
func getGraphList(tipe reflect.Type) *List {
79+
if tipe.Kind() == reflect.Slice {
80+
switch tipe.Elem().Kind() {
81+
case reflect.Int:
82+
case reflect.Int8:
83+
case reflect.Int32:
84+
case reflect.Int64:
85+
return NewList(Int)
86+
case reflect.Bool:
87+
return NewList(Boolean)
88+
case reflect.Float32:
89+
case reflect.Float64:
90+
return NewList(Float)
91+
case reflect.String:
92+
return NewList(String)
93+
}
94+
}
95+
// finaly bind object
96+
t := reflect.New(tipe.Elem())
97+
name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1)
98+
obj := NewObject(ObjectConfig{
99+
Name: name,
100+
Fields: BindFields(t.Elem().Interface()),
101+
})
102+
return NewList(obj)
103+
}
104+
105+
func appendFields(dest, origin Fields) Fields {
106+
for key, value := range origin {
107+
dest[key] = value
108+
}
109+
return dest
110+
}
111+
112+
func extractValue(originTag string, obj interface{}) interface{} {
113+
val := reflect.ValueOf(obj)
114+
115+
for j := 0; j < val.NumField(); j++ {
116+
typeField := val.Type().Field(j)
117+
if typeField.Type.Kind() == reflect.Struct {
118+
res := extractValue(originTag, val.Field(j).Interface())
119+
if res != nil {
120+
return res
121+
}
122+
}
123+
curTag := typeField.Tag
124+
if originTag == curTag.Get(TAG) {
125+
return val.Field(j).Interface()
126+
}
127+
}
128+
return nil
129+
}
130+
131+
// lazy way of binding args
132+
func BindArg(obj interface{}, tags ...string) FieldConfigArgument {
133+
v := reflect.ValueOf(obj)
134+
var config = make(FieldConfigArgument)
135+
for i := 0; i < v.NumField(); i++ {
136+
typeField := v.Type().Field(i)
137+
138+
mytag := typeField.Tag.Get(TAG)
139+
if inArray(tags, mytag) {
140+
config[mytag] = &ArgumentConfig{
141+
Type: getGraphType(typeField.Type),
142+
}
143+
}
144+
}
145+
return config
146+
}
147+
148+
func inArray(slice interface{}, item interface{}) bool {
149+
s := reflect.ValueOf(slice)
150+
if s.Kind() != reflect.Slice {
151+
panic("inArray() given a non-slice type")
152+
}
153+
154+
for i := 0; i < s.Len(); i++ {
155+
if reflect.DeepEqual(item, s.Index(i).Interface()) {
156+
return true
157+
}
158+
}
159+
return false
160+
}

util_test.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package graphql_test
2+
3+
import (
4+
"encoding/json"
5+
"log"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/graphql-go/graphql"
10+
"github.com/graphql-go/graphql/testutil"
11+
)
12+
13+
type Person struct {
14+
Human
15+
Name string `json:"name"`
16+
Home Address `json:"home"`
17+
Hobbies []string `json:"hobbies"`
18+
Friends []Friend `json:"friends"`
19+
}
20+
21+
type Human struct {
22+
Alive bool `json:"alive"`
23+
Age int `json:"age"`
24+
Weight float64 `json:"weight"`
25+
}
26+
27+
type Friend struct {
28+
Name string `json:"name"`
29+
Address string `json:"address"`
30+
}
31+
32+
type Address struct {
33+
Street string `json:"street"`
34+
City string `json:"city"`
35+
}
36+
37+
var personSource = Person{
38+
Human: Human{
39+
Age: 24,
40+
Weight: 70.1,
41+
Alive: true,
42+
},
43+
Name: "John Doe",
44+
Home: Address{
45+
Street: "Jl. G1",
46+
City: "Jakarta",
47+
},
48+
Friends: friendSource,
49+
Hobbies:[]string{"eat","sleep","code"},
50+
}
51+
52+
var friendSource = []Friend{
53+
{Name: "Arief", Address: "palembang"},
54+
{Name: "Al", Address: "semarang"},
55+
}
56+
57+
func TestBindFields(t *testing.T) {
58+
// create person type based on Person struct
59+
personType := graphql.NewObject(graphql.ObjectConfig{
60+
Name: "Person",
61+
// pass empty Person struct to bind all of it's fields
62+
Fields: graphql.BindFields(Person{}),
63+
})
64+
fields := graphql.Fields{
65+
"person": &graphql.Field{
66+
Type: personType,
67+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
68+
return personSource, nil
69+
},
70+
},
71+
}
72+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
73+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
74+
schema, err := graphql.NewSchema(schemaConfig)
75+
if err != nil {
76+
log.Fatalf("failed to create new schema, error: %v", err)
77+
}
78+
79+
// Query
80+
query := `
81+
{
82+
person{
83+
name,
84+
home{street,city},
85+
friends{name,address},
86+
age,
87+
weight,
88+
alive,
89+
hobbies
90+
}
91+
}
92+
`
93+
params := graphql.Params{Schema: schema, RequestString: query}
94+
r := graphql.Do(params)
95+
if len(r.Errors) > 0 {
96+
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
97+
}
98+
99+
rJSON, _ := json.Marshal(r)
100+
data := struct {
101+
Data struct {
102+
Person Person `json:"person"`
103+
} `json:"data"`
104+
}{}
105+
err = json.Unmarshal(rJSON, &data)
106+
if err != nil {
107+
log.Fatalf("failed to unmarshal. error: %v", err)
108+
}
109+
110+
newPerson := data.Data.Person
111+
if !reflect.DeepEqual(newPerson, personSource) {
112+
t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(personSource, newPerson))
113+
}
114+
}
115+
116+
func TestBindArg(t *testing.T) {
117+
var friendObj = graphql.NewObject(graphql.ObjectConfig{
118+
Name: "friend",
119+
Fields: graphql.BindFields(Friend{}),
120+
})
121+
122+
fields := graphql.Fields{
123+
"friend": &graphql.Field{
124+
Type: friendObj,
125+
//it can be added more than one since it's a slice
126+
Args: graphql.BindArg(Friend{}, "name"),
127+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
128+
if name, ok := p.Args["name"].(string); ok {
129+
for _, friend := range friendSource {
130+
if friend.Name == name {
131+
return friend, nil
132+
}
133+
}
134+
}
135+
return nil, nil
136+
},
137+
},
138+
}
139+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
140+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
141+
schema, err := graphql.NewSchema(schemaConfig)
142+
if err != nil {
143+
log.Fatalf("failed to create new schema, error: %v", err)
144+
}
145+
146+
// Query
147+
query := `
148+
{
149+
friend(name:"Arief"){
150+
address
151+
}
152+
}
153+
`
154+
params := graphql.Params{Schema: schema, RequestString: query}
155+
r := graphql.Do(params)
156+
if len(r.Errors) > 0 {
157+
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
158+
}
159+
160+
rJSON, _ := json.Marshal(r)
161+
162+
data := struct {
163+
Data struct {
164+
Friend Friend `json:"friend"`
165+
} `json:"data"`
166+
}{}
167+
err = json.Unmarshal(rJSON, &data)
168+
if err != nil {
169+
log.Fatalf("failed to unmarshal. error: %v", err)
170+
}
171+
172+
expectedAddress := "palembang"
173+
newFriend := data.Data.Friend
174+
if newFriend.Address != expectedAddress {
175+
t.Fatalf("Unexpected result, expected address to be %s but got %s", expectedAddress, newFriend.Address)
176+
}
177+
}

0 commit comments

Comments
 (0)