-
Notifications
You must be signed in to change notification settings - Fork 6
/
struct.go
121 lines (106 loc) · 3 KB
/
struct.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
package param
import (
"reflect"
"strings"
"sync"
)
// We decode a lot of structs (since it's the top-level thing this library
// decodes) and it takes a fair bit of work to reflect upon the struct to figure
// out what we want to do. Instead of doing this on every invocation, we cache
// metadata about each struct the first time we see it. The upshot is that we
// save some work every time. The downside is we are forced to briefly acquire
// a lock to access the cache in a thread-safe way. If this ever becomes a
// bottleneck, both the lock and the cache can be sharded or something.
type structCache map[string]cacheLine
type cacheLine struct {
offset int
parse func(string, string, []string, reflect.Value)
}
var cacheLock sync.RWMutex
var cache = make(map[reflect.Type]structCache)
func cacheStruct(t reflect.Type) structCache {
cacheLock.RLock()
sc, ok := cache[t]
cacheLock.RUnlock()
if ok {
return sc
}
// It's okay if two people build struct caches simultaneously
sc = make(structCache)
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
// Only unexported fields have a PkgPath; we want to only cache
// exported fields.
if sf.PkgPath != "" && !sf.Anonymous {
continue
}
name := extractName(sf)
if name != "-" {
sc[name] = cacheLine{i, extractHandler(t, sf)}
}
}
cacheLock.Lock()
cache[t] = sc
cacheLock.Unlock()
return sc
}
// Extract the name of the given struct field, looking at struct tags as
// appropriate.
func extractName(sf reflect.StructField) string {
name := sf.Tag.Get("param")
if name == "" {
name = sf.Tag.Get("json")
idx := strings.IndexRune(name, ',')
if idx >= 0 {
name = name[:idx]
}
}
if name == "" {
name = sf.Name
}
return name
}
func extractHandler(s reflect.Type, sf reflect.StructField) func(string, string, []string, reflect.Value) {
if reflect.PtrTo(sf.Type).Implements(textUnmarshalerType) {
return parseTextUnmarshaler
}
switch sf.Type.Kind() {
case reflect.Bool:
return parseBool
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return parseInt
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return parseUint
case reflect.Float32, reflect.Float64:
return parseFloat
case reflect.Map:
return parseMap
case reflect.Ptr:
return parsePtr
case reflect.Slice:
return parseSlice
case reflect.String:
return parseString
case reflect.Struct:
return parseStruct
default:
pebkac("struct %v has illegal field %q (type %v, kind %v).",
s, sf.Name, sf.Type, sf.Type.Kind())
return nil
}
}
// We have to parse two types of structs: ones at the top level, whose keys
// don't have square brackets around them, and nested structs, which do.
func parseStructField(cache structCache, key, sk, keytail string, values []string, target reflect.Value) {
l, ok := cache[sk]
if !ok {
panic(KeyError{
FullKey: key,
Key: kpath(key, keytail),
Type: target.Type(),
Field: sk,
})
}
f := target.Field(l.offset)
l.parse(key, keytail, values, f)
}