forked from goji/param
-
Notifications
You must be signed in to change notification settings - Fork 0
/
struct.go
127 lines (112 loc) · 3.16 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
122
123
124
125
126
127
package param
import (
"fmt"
"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) error
}
var cacheLock sync.RWMutex
var cache = make(map[reflect.Type]structCache)
func cacheStruct(t reflect.Type) (structCache, error) {
cacheLock.RLock()
sc, ok := cache[t]
cacheLock.RUnlock()
if ok {
return sc, nil
}
// 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 != "-" {
h, err := extractHandler(t, sf)
if err != nil {
return nil, err
}
sc[name] = cacheLine{i, h}
}
}
cacheLock.Lock()
cache[t] = sc
cacheLock.Unlock()
return sc, nil
}
// 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) error, error) {
if reflect.PtrTo(sf.Type).Implements(textUnmarshalerType) {
return parseTextUnmarshaler, nil
}
switch sf.Type.Kind() {
case reflect.Bool:
return parseBool, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return parseInt, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return parseUint, nil
case reflect.Float32, reflect.Float64:
return parseFloat, nil
case reflect.Map:
return parseMap, nil
case reflect.Ptr:
return parsePtr, nil
case reflect.Slice:
return parseSlice, nil
case reflect.String:
return parseString, nil
case reflect.Struct:
return parseStruct, nil
default:
return nil, InvalidParseError{
Type: s,
Hint: fmt.Sprintf("field %q in struct %v", sf.Name, s),
}
}
}
// 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) error {
l, ok := cache[sk]
if !ok {
return KeyError{
FullKey: key,
Key: kpath(key, keytail),
Type: target.Type(),
Field: sk,
}
}
f := target.Field(l.offset)
return l.parse(key, keytail, values, f)
}