-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgolf.go
472 lines (397 loc) · 10 KB
/
golf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
// Golf (Go List Fields) enables retrieving a map of an object's field
// names and values through either interface implementation or
// reflection.
package golf
import (
"fmt"
"log"
"os"
"reflect"
"strconv"
"strings"
)
var isDebug bool
func init() {
isDebug, _ = strconv.ParseBool(os.Getenv("GOLF_DEBUG"))
}
func debug(format string, args ...interface{}) {
if isDebug {
log.Printf(format, args...)
}
}
const (
// A field's JSON and Golf tags are both inspected with preference given to
// the Golf tags. This is the default.
PreferGolfTags = iota
// A field's JSON and Golf tags are both inspected with preference given to
// the JSON tags.
PreferJsonTags
// A field's JSON tags are ignored and only the Golf tags are used.
IgnoreJsonTags
// A field's Golf tags are ignored and only the JSON tags are used.
IgnoreGolfTags
)
const (
// One of the following types:
//
// - Array
// - Chan
// - Func
// - Interface
// - Map
// - Array
// - Ptr
// - Slice
// - UnsafePointer
NilKind = iota
// One of the following types:
//
// - Int
// - Int8
// - Int16
// - Int32
// - Int64
// - Uint
// - Uint8
// - Uint16
// - Uint32
// - Uint64
// - Uintptr
// - Float32
// - Float64
// - Complex64
// - Complex128
ValueKind
// A string type
StringKind
// A struct type
StructKind
)
// Golfs is an interface implemented by types in order to indicate to external
// packages that the type is aware that Golf may be used, thus giving the
// external package a way to test if a type is golf-aware.
type Golfs interface {
// PlayGolf is a dummy function that has no impact on anything other than
// supplying an empty interface with a way to be implemented.
PlayGolf() bool
}
// GolfsWithExportedFields is an interface implemented by types in order to
// return a map of explicit field names and values.
//
// If this function returns nil, and the inspected type is a struct then
// reflection is used to infer the object's field names and values.
type GolfsWithExportedFields interface {
// GolfExportedFields returns a map of the exported field names and their
// values.
GolfExportedFields() map[string]interface{}
}
// GolfsWithJsonTagBehavior is an interface impelemented by types in order to
// influence the logic of how a struct's field's tags are handled when
// reflection is used to infer an object's field names and values. This
// interface is only valid for struct types and will be ignored for others.
type GolfsWithJsonTagBehavior interface {
// GolfJsonTagBehavior returns an integer that dictates how a field's
// possible JSON tags should be treated.
//
// 0 - A field's JSON and Golf tags are both inspected with preference
// given to the Golf tags.
//
// 1 - A field's JSON and Golf tags are both inspected with preference
// given to the JSON tags.
//
// 2 - A field's JSON tags are ignored and only the Golf tags are used.
//
// 3 - A field's Golf tags are ignored and only the JSON tags are used.
GolfJsonTagBehavior() int
}
// Fore produces a map or an object's field names and values using the provided
// key as the prefix for any field name.
// This function will inspect an object to see if it implements the
// GolfsWithExportedFields interface first and foremost in order to enable
// non-struct types to return a map of field name/value data from the
// GolfExportedFields function.
//
// However, if the GolfExportedFields function returns nil and the provided
// object is a struct, reflection will be used to gather information about
// field names and values.
//
// Regardless of whether the GolfExportedFields map or data gathered through
// reflection is used, this function will recurse into the top-level data in
// order to map out the object graph, producing key/value pairs and adding them
// to the returned map for the entire graph.
func Fore(key string, val interface{}) map[string]interface{} {
if isNil(val) {
return nil
}
bk9 := map[string]interface{}{}
fore(key, val, bk9)
return bk9
}
func fore(key string, val interface{}, bk9 map[string]interface{}) {
// get val's golf tips
ef, jtb := caddy(val)
// if the value is not a struct or a pointer to a struct and there are
// no explicit exported fields then place the value in the map and return
isaStruct := isStruct(val)
if !isaStruct && ef == nil {
bk9[key] = val
return
}
// if the value is a struct or a pointer to a struct and there are no
// exported fields then use reflection to inspect the struct's fields
if isaStruct && ef == nil {
ef = map[string]interface{}{}
// get the value's type
vt := reflect.ValueOf(val)
if vt.Kind() == reflect.Ptr {
vt = vt.Elem()
}
// get the value's type's type
tt := vt.Type()
// populate a map with the value's exported field names and values
for x := 0; x < vt.NumField(); x++ {
ttf := tt.Field(x)
omit, fieldName, omitEmpty := parseField(&ttf, jtb)
if omit {
continue
}
vtf := vt.Field(x)
// only work with exported fields
if !vtf.CanInterface() {
continue
}
ie, vv, vk := parseValue(&vtf)
if omitEmpty && (vk == StringKind || vk == NilKind) && ie {
continue
}
ef[fieldName] = vv
}
}
for k, v := range ef {
kk := fmt.Sprintf("%s.%s", key, k)
rv := reflect.ValueOf(v)
ie, vv, vk := parseValue(&rv)
if vk == NilKind && ie {
bk9[kk] = vv
continue
}
if vk == StringKind || vk == ValueKind {
bk9[kk] = vv
}
fore(kk, vv, bk9)
}
}
func parseField(
field *reflect.StructField,
tagBehavior int) (omit bool, name string, omitEmpty bool) {
name = field.Name
tagPtr := &field.Tag
switch tagBehavior {
case IgnoreJsonTags:
parseTag(tagPtr, "golf", &omit, &name, &omitEmpty)
case IgnoreGolfTags:
parseTag(tagPtr, "json", &omit, &name, &omitEmpty)
case PreferJsonTags:
ios, ins, ies := parseTag(
tagPtr, "json", &omit, &name, &omitEmpty)
var omitPtr *bool
if !ios {
omitPtr = &omit
}
var namePtr *string
if !ins {
namePtr = &name
}
var omitEmptyPtr *bool
if !ies {
omitEmptyPtr = &omitEmpty
}
parseTag(
tagPtr, "golf", omitPtr, namePtr, omitEmptyPtr)
case PreferGolfTags:
ios, ins, ies := parseTag(
tagPtr, "golf", &omit, &name, &omitEmpty)
var omitPtr *bool
if !ios {
omitPtr = &omit
}
var namePtr *string
if !ins {
namePtr = &name
}
var omitEmptyPtr *bool
if !ies {
omitEmptyPtr = &omitEmpty
}
parseTag(
tagPtr, "json", omitPtr, namePtr, omitEmptyPtr)
}
debug("field %s omit=%v omitEmpty=%v", name, omit, omitEmpty)
return
}
func caddy(val interface{}) (map[string]interface{}, int) {
var ef map[string]interface{}
var jtb int
switch vt := val.(type) {
case GolfsWithExportedFields:
ef = vt.GolfExportedFields()
default:
ef = nil
}
switch vt := val.(type) {
case GolfsWithJsonTagBehavior:
jtb = vt.GolfJsonTagBehavior()
default:
jtb = PreferGolfTags
}
return ef, jtb
}
func parseTag(
tag *reflect.StructTag,
tagName string,
omit *bool,
name *string,
omitEmpty *bool) (isOmitSet, isNameSet, isOmitEmptySet bool) {
isOmitSet = false
isNameSet = false
isOmitEmptySet = false
if omit == nil && name == nil && omitEmpty == nil {
return
}
t := tag.Get(tagName)
if t == "" {
return
}
debug("parsing %s tag %s", tagName, t)
tagParts := strings.Split(t, ",")
for x := 0; x < len(tagParts); x++ {
tp := tagParts[x]
switch x {
case 0:
if tp == "-" && omit != nil {
*omit = true
isOmitSet = true
} else if tp != "" && name != nil {
*name = tp
isNameSet = true
}
case 1:
if tp == "omitempty" && omitEmpty != nil {
*omitEmpty = true
isOmitEmptySet = true
} else if tp != "" && name != nil {
*name = tp
isNameSet = true
}
}
}
return
}
func parseValue(v *reflect.Value) (isEmpty bool, value interface{}, kind int) {
if isStringKind(v, &isEmpty, &value) {
kind = StringKind
} else if isStructKind(v, &isEmpty, &value) {
kind = StructKind
} else if isValueKind(v, &isEmpty, &value) {
kind = ValueKind
} else if isNilKind(v, &isEmpty, &value) {
kind = NilKind
}
return
}
func isStringKind(v *reflect.Value, isEmpty *bool, val *interface{}) bool {
switch v.Kind() {
case reflect.String:
if val != nil && v.CanInterface() {
*val = v.String()
}
if isEmpty != nil {
*isEmpty = *val == ""
}
return true
}
return false
}
func isStructKind(v *reflect.Value, isEmpty *bool, val *interface{}) bool {
switch v.Kind() {
case reflect.Struct:
if val != nil && v.CanInterface() {
*val = v.Interface()
}
if isEmpty != nil {
*isEmpty = *val == reflect.New(v.Type()).Elem().Interface()
}
return true
}
return false
}
func isStruct(v interface{}) bool {
// to determine if val is a struct we need to test its reflected kind and
// if that fails, also test to see if val is a pointer or interface that
// points to a struct
rv := reflect.ValueOf(v)
rk := rv.Kind()
isaStruct := rk == reflect.Struct
if !isaStruct && (rk == reflect.Interface || rk == reflect.Ptr) {
isaStruct = rv.Elem().Kind() == reflect.Struct
}
return isaStruct
}
func isValueKind(v *reflect.Value, isEmpty *bool, val *interface{}) bool {
switch v.Kind() {
case
reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128:
if val != nil && v.CanInterface() {
*val = v.Interface()
}
if isEmpty != nil {
*isEmpty = false
}
return true
}
return false
}
func isNilKind(v *reflect.Value, isEmpty *bool, val *interface{}) bool {
switch v.Kind() {
case
reflect.Array,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice,
reflect.UnsafePointer:
if val != nil && v.CanInterface() {
*val = v.Interface()
}
if isEmpty != nil {
*isEmpty = v.IsNil()
}
return true
}
return false
}
func isNil(v interface{}) bool {
if v == nil {
return true
}
var ie bool
rv := reflect.ValueOf(v)
return isNilKind(&rv, &ie, nil) && ie
}