-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathreflect.go
296 lines (268 loc) · 10.3 KB
/
reflect.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
package test
import (
"reflect"
"slices"
"unsafe"
)
// Getter is a generic interface that allows you to access unexported fields
// of a (pointer) struct by field name.
type Getter[T any] interface {
// Get returns the value of the field with the given name. If the name is
// empty, the stored target instance is returned.
Get(name string) any
}
// Setter is a generic fluent interface that allows you to modify unexported
// fields of a (pointer) struct by field name.
type Setter[T any] interface {
// Set sets the value of the field with the given name. If the name is empty,
// and of the same type the stored target instance is replaced by the given
// value.
Set(name string, value any) Setter[T]
// Build returns the created or modified target instance of the builder.
Build() T
}
// Finder is a generic interface that allows you to access unexported fields
// of a (pointer) struct by field name.
type Finder[T any] interface {
// Find returns the first value of a field from the given list of field
// names with a type matching the default value type. If the name list is
// empty or contains a star (`*`), the first matching field in order of the
// struct declaration is returned as fallback. If no matching field is
// found, the default value is returned.
Find(dflt any, names ...string) any
}
// Builder is a generic, partially fluent interface that allows you to access
// and modify unexported fields of a (pointer) struct by field name.
type Builder[T any] interface {
// Getter is a generic interface that allows you to access unexported fields
// of a (pointer) struct by field name.
Getter[T]
// Finder is a generic interface that allows you to access unexported fields
// of a (pointer) struct by field name.
Finder[T]
// Setter is a generic fluent interface that allows you to modify unexported
// fields of a (pointer) struct by field name.
Setter[T]
}
// Find returns the first value of a parameter field from the given list of
// field names with a type matching the default value type. If the name list
// is empty or contains a star (`*`), the first matching field in order of the
// struct declaration is returned as fallback. If no matching field is found,
// the default value is returned.
//
// The `param“ object can be a struct, a pointer to a struct, or an arbitrary
// value matching the default value type. In the last case, the arbitrary value
// is returned as is.
func Find[P, T any](param P, deflt T, names ...string) T {
pt, dt := reflect.TypeOf(param), reflect.TypeOf(deflt)
if pt.Kind() == dt.Kind() {
return reflect.ValueOf(param).Interface().(T)
} else if pt.Kind() == reflect.Struct {
return NewAccessor[P](param).Find(deflt, names...).(T)
} else if pt.Kind() == reflect.Ptr && pt.Elem().Kind() == reflect.Struct {
return NewAccessor[P](param).Find(deflt, names...).(T)
}
return deflt
}
// Builder is used for accessing and modifying unexported fields in a struct
// or a struct pointer.
type builder[T any] struct {
// target is the struct reflection value of the struct or struct pointer
// instance to be accessed and modified.
target any
// rtype is the targets reflection type of the struct or struct pointer
// instance to be accessed and modified.
rtype reflect.Type
// wrapped is true if the target is a struct that is actually wrapped in
// a pointer instance.
wrapped bool
}
// NewBuilder creates a generic builder for a target struct type. The builder
// allows you to access and modify unexported fields of the struct by field
// name.
func NewBuilder[T any]() Builder[T] {
var target T
return NewAccessor[T](target)
}
// NewGetter creates a generic getter for a target struct type. The getter
// allows you to access unexported fields of the struct by field name.
func NewGetter[T any](target T) Getter[T] {
return NewAccessor[T](target)
}
// NewSetter creates a generic setter for a target struct type. The setter
// allows you to modify unexported fields of the struct by field name.
func NewSetter[T any](target T) Setter[T] {
return NewAccessor[T](target)
}
// NewFinder creates a generic finder for a target struct type. The finder
// allows you to access unexported fields of the struct by field name.
func NewFinder[T any](target T) Finder[T] {
return NewAccessor[T](target)
}
// NewAccessor creates a generic builder/accessor for a given target struct.
// The builder allows you to access and modify unexported fields of the struct
// by field name.
//
// If the target is a pointer to a struct (template), the pointer is stored
// and the instance is modified directly. If the pointer is nil a new instance
// is created and stored for modification.
//
// If the target is a struct, it cannot be modified directly and a new pointer
// struct is created to circumvent the access restrictions on private fields.
// The pointer struct is stored for modification.
func NewAccessor[T any](target T) Builder[T] {
value := reflect.ValueOf(target)
if value.Kind() == reflect.Ptr {
// Create a new instance if the pointer is nil.
if value.Elem().Kind() == reflect.Invalid {
target = reflect.New(value.Type().Elem()).Interface().(T)
value = reflect.ValueOf(target)
}
if value.Elem().Kind() == reflect.Struct {
return &builder[T]{
target: target,
rtype: value.Elem().Type(),
wrapped: false,
}
}
} else if value.Kind() == reflect.Struct {
// Create a new pointer instance for modification.
value = reflect.New(value.Type())
value.Elem().Set(reflect.ValueOf(target))
return &builder[T]{
target: value.Interface(),
rtype: value.Elem().Type(),
wrapped: true,
}
}
panic("target must be struct or struct pointer [" +
typeOf(target) + "]")
}
// typeOf returns the type of the given target instance. If the target is nil,
// the type is "nil".
func typeOf(target any) string {
if target != nil {
return reflect.TypeOf(target).String()
}
return "nil"
}
// Set sets the value of the field with the given name. If the name is empty,
// and of the same type the stored target instance is replaced by the given
// value. If the value is nil, the field is set to the zero value of the field
// type. If the field is not found or the value is not assignable to it, a
// panic is raised.
func (b *builder[T]) Set(name string, value any) Setter[T] {
if name != "" {
b.set(name, value)
} else if value == nil && b.rtype.Kind() == reflect.Struct {
b.target = reflect.New(b.rtype).Interface()
} else if reflect.TypeOf(b.target) == reflect.TypeOf(value) {
b.target = value
} else {
panic("target must be compatible struct pointer [" +
typeOf(value) + " => " + typeOf(b.target) + "]")
}
return b
}
// Get returns the value of the field with the given name. If the name is
// empty, the stored target instance is returned.
func (b *builder[T]) Get(name string) any {
if name == "" {
return b.Build()
}
target := b.targetValueOf()
if !target.IsValid() {
if field, ok := b.rtype.FieldByName(name); ok {
return reflect.New(field.Type).Elem().Interface()
}
panic("target field not found [" + name + "]")
}
field := target.FieldByName(name)
if !field.IsValid() {
panic("target field not found [" + name + "]")
}
return b.valuePtr(field).Elem().Interface()
}
// Find returns the first value of a field from the given list of field names
// with a type matching the default value type. If the name list is empty or
// contains a star (`*`), the first matching field in order of the struct
// declaration is returned as fallback. If no matching field is found, the
// default value is returned.
func (b *builder[T]) Find(deflt any, names ...string) any {
for _, name := range names {
if field := b.targetValueOf().FieldByName(name); field.IsValid() {
if b.canBeAssigned(reflect.TypeOf(deflt), field.Type()) {
return b.valuePtr(field).Elem().Interface()
}
}
}
if len(names) == 0 || slices.Contains(names, "*") {
// Fallback to the first field with a matching type.
for i := 0; i < b.rtype.NumField(); i++ {
tfield := b.rtype.Field(i)
if b.canBeAssigned(reflect.TypeOf(deflt), tfield.Type) {
vfield := b.targetValueOf().Field(i)
return b.valuePtr(vfield).Elem().Interface()
}
}
}
return deflt
}
// Build returns the created/modified target instance of the builder/accessor.
func (b *builder[T]) Build() T {
if b.wrapped {
target := b.targetValueOf()
if target.IsValid() {
return target.Interface().(T)
}
var t T
return t
} else {
return b.target.(T)
}
}
// set sets the value of the field with the given name. If the value is nil,
// the field is set to the zero value of the field type. If the field is not
// found or the value is not assignable to it, a panic is raised.
func (b *builder[T]) set(name string, value any) {
field := b.targetValueOf().FieldByName(name)
if !field.IsValid() {
panic("target field not found [" + name + "]")
} else if !b.canBeAssigned(field.Type(), reflect.TypeOf(value)) {
panic("value must be compatible [" +
typeOf(value) + " => " + field.Type().String() + "]")
}
b.valuePtr(field).Elem().Set(b.valueOf(field, value))
}
// targetValueOf returns the reflect value of the target instance.
func (b *builder[T]) targetValueOf() reflect.Value {
return reflect.ValueOf(b.target).Elem()
}
// canBeAssigned returns true if the given value is compatible with the given
// field type. This is either the case if the value implements the field type
// or if the value is assignable to the field type. Nil value types are always
// assignable using the zero value.
func (builder[T]) canBeAssigned(field reflect.Type, value reflect.Type) bool {
if value == nil {
return true
} else if field.Kind() == reflect.Interface {
return value.Implements(field)
} else {
return value.AssignableTo(field)
}
}
// valuePtr returns the reflect value of the field as a pointer. This is
// required to set the value of a field, since reflect.Value.Set does not
// support setting unexported fields.
func (*builder[T]) valuePtr(field reflect.Value) reflect.Value {
// #nosec G103,G115 // This is a safe use of unsafe.Pointer.
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr()))
}
// valueOf returns the reflect value of the given value. If the value is nil,
// the zero value of the field type is returned.
func (*builder[T]) valueOf(field reflect.Value, value any) reflect.Value {
if value == nil {
return reflect.Zero(field.Type())
}
return reflect.ValueOf(value)
}