This repository has been archived by the owner on May 13, 2022. It is now read-only.
/
reflect_tagged.go
140 lines (126 loc) · 3.5 KB
/
reflect_tagged.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
package query
import (
"fmt"
"reflect"
"strings"
"sync"
)
type ReflectTagged struct {
rv reflect.Value
keys []string
ks map[string]struct{}
}
var _ Tagged = &ReflectTagged{}
func MustReflectTags(value interface{}, fieldNames ...string) *ReflectTagged {
rt, err := ReflectTags(value, fieldNames...)
if err != nil {
panic(err)
}
return rt
}
// ReflectTags provides a query.Tagged on a structs exported fields using query.StringFromValue to derive the string
// values associated with each field. If passed explicit field names will only only provide those fields as tags,
// otherwise all exported fields are provided.
func ReflectTags(value interface{}, fieldNames ...string) (*ReflectTagged, error) {
rv := reflect.ValueOf(value)
if rv.IsNil() {
return &ReflectTagged{}, nil
}
if rv.Kind() != reflect.Ptr {
return nil, fmt.Errorf("ReflectStructTags needs a pointer to a struct but %v is not a pointer",
rv.Interface())
}
if rv.Elem().Kind() != reflect.Struct {
return nil, fmt.Errorf("ReflectStructTags needs a pointer to a struct but %v does not point to a struct",
rv.Interface())
}
ty := rv.Elem().Type()
// Try our global cache on types
if rt, ok := cache.get(ty, fieldNames); ok {
rt.rv = rv
return rt, nil
}
numField := ty.NumField()
if len(fieldNames) > 0 {
if len(fieldNames) > numField {
return nil, fmt.Errorf("ReflectTags asked to tag %v fields but %v only has %v fields",
len(fieldNames), rv.Interface(), numField)
}
numField = len(fieldNames)
}
rt := &ReflectTagged{
rv: rv,
ks: make(map[string]struct{}, numField),
keys: make([]string, 0, numField),
}
if len(fieldNames) > 0 {
for _, fieldName := range fieldNames {
field, ok := ty.FieldByName(fieldName)
if !ok {
return nil, fmt.Errorf("ReflectTags asked to tag field named %s by no such field on %v",
fieldName, rv.Interface())
}
ok = rt.registerField(field)
if !ok {
return nil, fmt.Errorf("field %s of %v is not exported so cannot act as tag", fieldName,
rv.Interface())
}
}
} else {
for i := 0; i < numField; i++ {
rt.registerField(ty.Field(i))
}
}
// Cache the registration
cache.put(ty, rt, fieldNames)
return rt, nil
}
func (rt *ReflectTagged) registerField(field reflect.StructField) (ok bool) {
// Empty iff struct field is exported
if field.PkgPath == "" {
rt.keys = append(rt.keys, field.Name)
rt.ks[field.Name] = struct{}{}
return true
}
return false
}
func (rt *ReflectTagged) Keys() []string {
return rt.keys
}
func (rt *ReflectTagged) Get(key string) (value string, ok bool) {
if _, ok := rt.ks[key]; ok {
return StringFromValue(rt.rv.Elem().FieldByName(key).Interface()), true
}
return "", false
}
func (rt *ReflectTagged) Len() int {
return len(rt.keys)
}
type reflectTaggedCache struct {
sync.Mutex
rts map[reflect.Type]map[string]ReflectTagged
}
// Avoid the need to iterate over reflected type each time we need a reflect tagged
var cache = &reflectTaggedCache{
rts: make(map[reflect.Type]map[string]ReflectTagged),
}
func (c *reflectTaggedCache) get(ty reflect.Type, keys []string) (*ReflectTagged, bool) {
c.Lock()
defer c.Unlock()
if _, ok := c.rts[ty]; ok {
key := strings.Join(keys, ",")
if rt, ok := c.rts[ty][key]; ok {
return &rt, true
}
}
return nil, false
}
func (c *reflectTaggedCache) put(ty reflect.Type, rt *ReflectTagged, fieldNames []string) {
c.Lock()
defer c.Unlock()
if _, ok := c.rts[ty]; !ok {
c.rts[ty] = make(map[string]ReflectTagged)
}
key := strings.Join(fieldNames, ",")
c.rts[ty][key] = *rt
}