-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathparser.go
More file actions
198 lines (168 loc) · 4.34 KB
/
parser.go
File metadata and controls
198 lines (168 loc) · 4.34 KB
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
package anonymizer
import (
"fmt"
"reflect"
"strings"
"sync"
)
const (
// defined anonymizer decorator name for struct tag
anonymizerTagName = "anonymizer"
// specifies if the field should be skipped from anonymization
skipField = "skip"
)
// structField represents a single field found in a struct
type structField struct {
// field name
Name string
// field index path
Index []int
// field type
Type reflect.Type
// whether to skip anonymization
Skip bool
}
// structInfo holds information about a struct for anonymization
type structInfo struct {
Fields []structField
Type reflect.Type
}
var (
// cache for struct information
structCache = make(map[reflect.Type]*structInfo)
cacheMutex sync.RWMutex
)
// ErrObjectImmutable is returned when trying to anonymize an immutable object
var ErrObjectImmutable = fmt.Errorf("object is immutable and cannot be anonymized")
// getStructInfo returns struct information from cache or parses it
func getStructInfo(t reflect.Type) (*structInfo, error) {
cacheMutex.RLock()
info, exists := structCache[t]
cacheMutex.RUnlock()
if exists {
return info, nil
}
// parse struct and cache result
cacheMutex.Lock()
defer cacheMutex.Unlock()
// double-check after acquiring write lock
if info, exists := structCache[t]; exists {
return info, nil
}
info, err := parseStruct(t)
if err != nil {
return nil, err
}
structCache[t] = info
return info, nil
}
// parseStruct parses struct fields and returns StructInfo
func parseStruct(t reflect.Type) (*structInfo, error) {
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct type, got %s", t.Kind())
}
fields, err := getStructFields(t)
if err != nil {
return nil, err
}
return &structInfo{
Fields: fields,
Type: t,
}, nil
}
// getStructFields returns a list of fields for the given struct type
func getStructFields(t reflect.Type) ([]structField, error) {
// anonymous fields to explore at current and next level
current := []structField{}
next := []structField{{Type: t}}
// count of queued names for current level and the next
var count, nextCount map[reflect.Type]int
// types already visited at an earlier level
visited := map[reflect.Type]bool{}
// fields found
var fields []structField
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.Type] {
continue
}
visited[f.Type] = true
// scan f.Type for fields to include
for i := 0; i < f.Type.NumField(); i++ {
sf := f.Type.Field(i)
if sf.Anonymous {
t := sf.Type
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if !sf.IsExported() && t.Kind() != reflect.Struct {
// ignore embedded fields of unexported non-struct types
continue
}
} else if !sf.IsExported() {
// ignore unexported non-embedded fields
continue
}
// parse anonymizer tag
var skip bool
tagAnonymizer := sf.Tag.Get(anonymizerTagName)
if tagAnonymizer != "" {
opts := strings.Split(tagAnonymizer, ",")
for _, opt := range opts {
if strings.TrimSpace(opt) == skipField {
skip = true
break
}
}
}
index := make([]int, len(f.Index)+1)
copy(index, f.Index)
index[len(f.Index)] = i
ft := sf.Type
for ft.Name() == "" && ft.Kind() == reflect.Pointer {
// follow pointer
ft = ft.Elem()
}
// record found field and index sequence
if !sf.Anonymous || ft.Kind() != reflect.Struct {
field := structField{
Name: sf.Name,
Index: index,
Type: ft,
Skip: skip,
}
fields = append(fields, field)
if count[f.Type] > 1 {
// if there were multiple instances, add a second
fields = append(fields, fields[len(fields)-1])
}
continue
}
// record new anonymous struct to explore in next round
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, structField{Name: ft.Name(), Index: index, Type: ft})
}
}
}
}
return fields, nil
}
// isMutable checks if the given value can be modified
func isMutable(v reflect.Value) bool {
switch v.Kind() {
case reflect.Pointer:
return !v.IsNil()
case reflect.Map, reflect.Slice:
return true
case reflect.Interface:
if !v.IsNil() {
return isMutable(v.Elem())
}
return false
default:
return false
}
}