-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathfield.go
188 lines (168 loc) · 5.11 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
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
package fig
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)
}
}
case reflect.Map:
for _, key := range f.v.MapKeys() {
child := newMapField(f, key, tagKey)
*fs = append(*fs, child)
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, // not applicable for struct fields
}
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
}
// newMapField is a constructor for a field that is a map entry.
// key is the key of the map entry, and tagKey is the key of the
// tag that contains the field alt name (if any).
func newMapField(parent *field, key reflect.Value, tagKey string) *field {
f := &field{
parent: parent,
v: parent.v.MapIndex(key),
t: parent.v.MapIndex(key).Type(),
st: parent.st,
sliceIdx: -1, // not applicable for map entries
mapKey: &key,
}
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.
mapKey *reflect.Value // key of the map entry if this field is a map entry, nil otherwise.
structTag
}
// name is the name of the field. if the field contains an alt name
// in the 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.mapKey != nil {
return fmt.Sprintf("[%s]", f.mapKey.String())
}
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 && f.t.Kind() != reflect.Map {
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.
}