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 != "" {
|
|
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 {
|
|
perr("unknown key %q for struct at key %q", sk,
|
|
kpath(key, keytail))
|
|
}
|
|
f := target.Field(l.offset)
|
|
|
|
l.parse(key, keytail, values, f)
|
|
}
|