-
Notifications
You must be signed in to change notification settings - Fork 1
/
field.go
160 lines (143 loc) · 4.24 KB
/
field.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
package confucius
import (
"fmt"
"reflect"
"strings"
)
// flattenCfg recursively flattens a cfg struct into
// a slice of its constituent fields.
func flattenCfg(cfg interface{}, tagKey string) []*field {
root := &field{
v: reflect.ValueOf(cfg).Elem(),
t: reflect.ValueOf(cfg).Elem().Type(),
sliceIdx: -1,
}
fs := make([]*field, 0)
flattenField(root, &fs, tagKey)
return fs
}
// flattenField recursively flattens a field into its
// constituent fields, filling fs as it goes.
func flattenField(f *field, fs *[]*field, tagKey string) {
for (f.v.Kind() == reflect.Ptr || f.v.Kind() == reflect.Interface) && !f.v.IsNil() {
f.v = f.v.Elem()
f.t = f.v.Type()
}
switch f.v.Kind() {
case reflect.Struct:
for i := 0; i < f.t.NumField(); i++ {
unexported := f.t.Field(i).PkgPath != ""
embedded := f.t.Field(i).Anonymous
if unexported && !embedded {
continue
}
child := newStructField(f, i, tagKey)
*fs = append(*fs, child)
flattenField(child, fs, tagKey)
}
case reflect.Slice, reflect.Array:
switch f.t.Elem().Kind() {
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface:
for i := 0; i < f.v.Len(); i++ {
child := newSliceField(f, i, tagKey)
flattenField(child, fs, tagKey)
}
}
}
}
// newStructField is a constructor for a field that is a struct
// member. idx is the field's index in the struct. tagKey is the
// key of the tag that contains the field alt name (if any).
func newStructField(parent *field, idx int, tagKey string) *field {
f := &field{
parent: parent,
v: parent.v.Field(idx),
t: parent.v.Field(idx).Type(),
st: parent.t.Field(idx),
sliceIdx: -1,
}
f.structTag = parseTag(f.st.Tag, tagKey)
return f
}
// newStructField is a constructor for a field that is a slice
// member. idx is the field's index in the slice. tagKey is the
// key of the tag that contains the field alt name (if any).
func newSliceField(parent *field, idx int, tagKey string) *field {
f := &field{
parent: parent,
v: parent.v.Index(idx),
t: parent.v.Index(idx).Type(),
st: parent.st,
sliceIdx: idx,
}
f.structTag = parseTag(f.st.Tag, tagKey)
return f
}
// field is a settable field of a config object.
type field struct {
parent *field
v reflect.Value
t reflect.Type
st reflect.StructField
sliceIdx int // >=0 if this field is a member of a slice.
structTag
}
// name is the name of the field. if the field contains an alt name
// in the struct struct that name is used, else it falls back to
// the field's name as defined in the struct.
// if this field is a slice field, then its name is simply its
// index in the slice.
func (f *field) name() string {
if f.sliceIdx >= 0 {
return fmt.Sprintf("[%d]", f.sliceIdx)
}
if f.altName != "" {
return f.altName
}
return f.st.Name
}
// path is a dot separated path consisting of all the names of
// the field's ancestors starting from the topmost parent all the
// way down to the field itself.
func (f *field) path() (path string) {
var visit func(f *field)
visit = func(f *field) {
if f.parent != nil {
visit(f.parent)
}
path += f.name()
// if it's a slice/array we don't want a dot before the slice indexer
// e.g. we want A[0].B instead of A.[0].B
if f.t.Kind() != reflect.Slice && f.t.Kind() != reflect.Array {
path += "."
}
}
visit(f)
return strings.Trim(path, ".")
}
// parseTag parses a fields struct tags into a more easy to use structTag.
// key is the key of the struct tag which contains the field's alt name.
func parseTag(tag reflect.StructTag, key string) (st structTag) {
if val, ok := tag.Lookup(key); ok {
i := strings.Index(val, ",")
if i == -1 {
i = len(val)
}
st.altName = val[:i]
}
if val := tag.Get("validate"); val == "required" {
st.required = true
}
if val, ok := tag.Lookup("default"); ok {
st.setDefault = true
st.defaultVal = val
}
return
}
// structTag contains information gathered from parsing a field's tags.
type structTag struct {
altName string // the alt name of the field as defined in the tag.
required bool // true if the tag contained a required validation key.
setDefault bool // true if tag contained a default key.
defaultVal string // the value of the default key.
}