1package graphql
2
3import (
4 "fmt"
5 "reflect"
6 "strings"
7)
8
9const 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
17func 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 := extractTag(typeField.Tag)
25 if tag == "-" {
26 continue
27 }
28
29 var graphType Output
30 if typeField.Type.Kind() == reflect.Struct {
31
32 structFields := BindFields(v.Field(i).Interface())
33 if tag == "" {
34 fields = appendFields(fields, structFields)
35 continue
36 } else {
37 graphType = NewObject(ObjectConfig{
38 Name: tag,
39 Fields: structFields,
40 })
41 }
42 }
43
44 if tag == "" {
45 continue
46 }
47
48 if graphType == nil {
49 graphType = getGraphType(typeField.Type)
50 }
51 fields[tag] = &Field{
52 Type: graphType,
53 Resolve: func(p ResolveParams) (interface{}, error) {
54 return extractValue(tag, p.Source), nil
55 },
56 }
57 }
58 return fields
59}
60
61func getGraphType(tipe reflect.Type) Output {
62 kind := tipe.Kind()
63 switch kind {
64 case reflect.String:
65 return String
66 case reflect.Int:
67 fallthrough
68 case reflect.Int8:
69 fallthrough
70 case reflect.Int32:
71 fallthrough
72 case reflect.Int64:
73 return Int
74 case reflect.Float32:
75 fallthrough
76 case reflect.Float64:
77 return Float
78 case reflect.Bool:
79 return Boolean
80 case reflect.Slice:
81 return getGraphList(tipe)
82 }
83 return String
84}
85
86func getGraphList(tipe reflect.Type) *List {
87 if tipe.Kind() == reflect.Slice {
88 switch tipe.Elem().Kind() {
89 case reflect.Int:
90 fallthrough
91 case reflect.Int8:
92 fallthrough
93 case reflect.Int32:
94 fallthrough
95 case reflect.Int64:
96 return NewList(Int)
97 case reflect.Bool:
98 return NewList(Boolean)
99 case reflect.Float32:
100 fallthrough
101 case reflect.Float64:
102 return NewList(Float)
103 case reflect.String:
104 return NewList(String)
105 }
106 }
107 // finaly bind object
108 t := reflect.New(tipe.Elem())
109 name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1)
110 obj := NewObject(ObjectConfig{
111 Name: name,
112 Fields: BindFields(t.Elem().Interface()),
113 })
114 return NewList(obj)
115}
116
117func appendFields(dest, origin Fields) Fields {
118 for key, value := range origin {
119 dest[key] = value
120 }
121 return dest
122}
123
124func extractValue(originTag string, obj interface{}) interface{} {
125 val := reflect.ValueOf(obj)
126
127 for j := 0; j < val.NumField(); j++ {
128 typeField := val.Type().Field(j)
129 if typeField.Type.Kind() == reflect.Struct {
130 res := extractValue(originTag, val.Field(j).Interface())
131 if res != nil {
132 return res
133 }
134 }
135
136 if originTag == extractTag(typeField.Tag) {
137 return val.Field(j).Interface()
138 }
139 }
140 return nil
141}
142
143func extractTag(tag reflect.StructTag) string {
144 t := tag.Get(TAG)
145 if t != "" {
146 t = strings.Split(t, ",")[0]
147 }
148 return t
149}
150
151// lazy way of binding args
152func BindArg(obj interface{}, tags ...string) FieldConfigArgument {
153 v := reflect.ValueOf(obj)
154 var config = make(FieldConfigArgument)
155 for i := 0; i < v.NumField(); i++ {
156 typeField := v.Type().Field(i)
157
158 mytag := extractTag(typeField.Tag)
159 if inArray(tags, mytag) {
160 config[mytag] = &ArgumentConfig{
161 Type: getGraphType(typeField.Type),
162 }
163 }
164 }
165 return config
166}
167
168func inArray(slice interface{}, item interface{}) bool {
169 s := reflect.ValueOf(slice)
170 if s.Kind() != reflect.Slice {
171 panic("inArray() given a non-slice type")
172 }
173
174 for i := 0; i < s.Len(); i++ {
175 if reflect.DeepEqual(item, s.Index(i).Interface()) {
176 return true
177 }
178 }
179 return false
180}