forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
field.go
220 lines (187 loc) · 5.37 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
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
package field
import (
"reflect"
)
// Field is a controller's field
// contains all the necessary, internal, information
// to work with.
type Field struct {
Name string // the field's original name
// but if a tag with `name: "other"`
// exist then this fill is filled, otherwise it's the same as the Name.
TagName string
Index int
Type reflect.Type
Value reflect.Value
embedded *Field
}
// GetIndex returns all the "dimensions"
// of the controller struct field's position that this field is referring to,
// recursively.
// Usage: elem.FieldByIndex(field.getIndex())
// for example the {0,1} means that the field is on the second field of the first's
// field of this struct.
func (ff Field) GetIndex() []int {
deepIndex := []int{ff.Index}
if emb := ff.embedded; emb != nil {
deepIndex = append(deepIndex, emb.GetIndex()...)
}
return deepIndex
}
// GetType returns the type of the referring field, recursively.
func (ff Field) GetType() reflect.Type {
typ := ff.Type
if emb := ff.embedded; emb != nil {
return emb.GetType()
}
return typ
}
// GetFullName returns the full name of that field
// i.e: UserController.SessionController.Manager,
// it's useful for debugging only.
func (ff Field) GetFullName() string {
name := ff.Name
if emb := ff.embedded; emb != nil {
return name + "." + emb.GetFullName()
}
return name
}
// GetTagName returns the tag name of the referring field
// recursively.
func (ff Field) GetTagName() string {
name := ff.TagName
if emb := ff.embedded; emb != nil {
return emb.GetTagName()
}
return name
}
// checkVal checks if that value
// is valid to be set-ed to the new controller's instance.
// Used on binder.
func checkVal(val reflect.Value) bool {
return val.IsValid() && (val.Kind() == reflect.Ptr && !val.IsNil()) && val.CanInterface()
}
// GetValue returns a valid value of the referring field, recursively.
func (ff Field) GetValue() interface{} {
if ff.embedded != nil {
return ff.embedded.GetValue()
}
if checkVal(ff.Value) {
return ff.Value.Interface()
}
return "undefinied value"
}
// SendTo should be used when this field or its embedded
// has a Value on it.
// It sets the field's value to the "elem" instance, it's the new controller.
func (ff Field) SendTo(elem reflect.Value) {
// note:
// we don't use the getters here
// because we do recursively search by our own here
// to be easier to debug if ever needed.
if embedded := ff.embedded; embedded != nil {
if ff.Index >= 0 {
embedded.SendTo(elem.Field(ff.Index))
}
return
}
elemField := elem.Field(ff.Index)
if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
return
}
elemField.Set(ff.Value)
}
// lookupTagName checks if the "elemField" struct's field
// contains a tag `name`, if it contains then it returns its value
// otherwise returns the field's original Name.
func lookupTagName(elemField reflect.StructField) string {
vname := elemField.Name
if taggedName, ok := elemField.Tag.Lookup("name"); ok {
vname = taggedName
}
return vname
}
// LookupFields iterates all "elem"'s fields and its fields
// if structs, recursively.
// Compares them to the "matcher", if they passed
// then it executes the "handler" if any,
// the handler can change the field as it wants to.
//
// It finally returns that collection of the valid fields, can be empty.
func LookupFields(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*Field)) (fields []Field) {
for i, n := 0, elem.NumField(); i < n; i++ {
elemField := elem.Field(i)
if matcher(elemField) {
field := Field{
Index: i,
Name: elemField.Name,
TagName: lookupTagName(elemField),
Type: elemField.Type,
}
if handler != nil {
handler(&field)
}
// we area inside the correct type
fields = append(fields, field)
continue
}
f := lookupStructField(elemField.Type, matcher, handler)
if f != nil {
fields = append(fields, Field{
Index: i,
Name: elemField.Name,
Type: elemField.Type,
embedded: f,
})
}
}
return
}
// lookupStructField is here to search for embedded field only,
// is working with the "lookupFields".
// We could just one one function
// for both structured (embedded) fields and normal fields
// but we keep that as it's, a new function like this
// is easier for debugging, if ever needed.
func lookupStructField(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*Field)) *Field {
// fmt.Printf("lookup struct for elem: %s\n", elem.Name())
// ignore if that field is not a struct
if elem.Kind() != reflect.Struct {
return nil
}
// search by fields.
for i, n := 0, elem.NumField(); i < n; i++ {
elemField := elem.Field(i)
if matcher(elemField) {
// we area inside the correct type.
f := &Field{
Index: i,
Name: elemField.Name,
TagName: lookupTagName(elemField),
Type: elemField.Type,
}
if handler != nil {
handler(f)
}
return f
}
// if field is struct and the value is struct
// then try inside its fields for a compatible
// field type.
if elemField.Type.Kind() == reflect.Struct { // 3-level
elemFieldEmb := elem.Field(i)
f := lookupStructField(elemFieldEmb.Type, matcher, handler)
if f != nil {
fp := &Field{
Index: i,
Name: elemFieldEmb.Name,
TagName: lookupTagName(elemFieldEmb),
Type: elemFieldEmb.Type,
embedded: f,
}
return fp
}
}
}
return nil
}