-
Notifications
You must be signed in to change notification settings - Fork 1
/
struct_walk.go
86 lines (73 loc) · 2.22 KB
/
struct_walk.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
package copre
import (
"fmt"
"reflect"
"strings"
"github.com/mitchellh/reflectwalk"
)
// FieldMapper is a function that takes the path of a field in a nested structure
// and field itself, to return a value or an error.
type FieldMapper func(path []string, field reflect.StructField) (interface{}, error)
// StructWalk walks/visits every field of a struct (including nested) and calls
// fieldMapper for every field. If an internal error is encountered or an error
// is returned by fieldMapper it is immediately returned and traversal stopped.
func StructWalk(dst interface{}, fieldMapper FieldMapper) error {
w := &structWalker{
Path: []string{},
FieldMapper: fieldMapper,
}
return reflectwalk.Walk(dst, w)
}
type structWalker struct {
Path []string
FieldMapper FieldMapper
}
func (w *structWalker) Enter(l reflectwalk.Location) error {
return nil
}
func (w *structWalker) Exit(l reflectwalk.Location) error {
if l == reflectwalk.Struct && len(w.Path) > 0 {
w.Path = w.Path[:len(w.Path)-1]
}
return nil
}
func (w *structWalker) Struct(v reflect.Value) error {
return nil
}
func (w *structWalker) StructField(sf reflect.StructField, v reflect.Value) (err error) {
if sf.Type.Kind() == reflect.Ptr {
// For pointers that are nil we try to set the default value
if v.IsNil() {
if !v.CanSet() {
return
}
ptr := reflect.New(sf.Type.Elem())
v.Set(ptr)
}
// We do not mutate pointers, so let's sets retrieve element
v = v.Elem()
}
// If type is struct or pointer to struct, append path
if sf.Type.Kind() == reflect.Struct || (sf.Type.Kind() == reflect.Ptr && sf.Type.Elem().Kind() == reflect.Struct) {
w.Path = append(w.Path, sf.Name)
return
}
var result interface{}
path := append(w.Path, sf.Name)
result, err = w.FieldMapper(path, sf)
if err != nil {
return
}
// If result is not nil, set it
// (playing it safe here using reflection)
if result == nil || (reflect.ValueOf(result).Kind() == reflect.Ptr && reflect.ValueOf(result).IsNil()) {
return nil
}
defer func() {
if recover() != nil {
err = fmt.Errorf("failed to set value at path '.%s': expected type '%s', got '%T'.", strings.Join(path, "."), v.Type().String(), result)
}
}()
v.Set(reflect.ValueOf(result))
return
}