478 lines
14 KiB
Go
478 lines
14 KiB
Go
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package extraconfig
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
nilValue = reflect.ValueOf(nil)
|
|
)
|
|
|
|
type decoder func(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error)
|
|
|
|
var (
|
|
kindDecoders map[reflect.Kind]decoder
|
|
intfDecoders map[reflect.Type]decoder
|
|
)
|
|
|
|
func init() {
|
|
kindDecoders = map[reflect.Kind]decoder{
|
|
reflect.String: decodeString,
|
|
reflect.Struct: decodeStruct,
|
|
reflect.Slice: decodeSlice,
|
|
reflect.Array: decodeSlice,
|
|
reflect.Map: decodeMap,
|
|
reflect.Ptr: decodePtr,
|
|
reflect.Int: decodePrimitive,
|
|
reflect.Int8: decodePrimitive,
|
|
reflect.Int16: decodePrimitive,
|
|
reflect.Int32: decodePrimitive,
|
|
reflect.Int64: decodePrimitive,
|
|
reflect.Bool: decodePrimitive,
|
|
reflect.Float32: decodePrimitive,
|
|
reflect.Float64: decodePrimitive,
|
|
}
|
|
|
|
intfDecoders = map[reflect.Type]decoder{
|
|
reflect.TypeOf(time.Time{}): decodeTime,
|
|
}
|
|
}
|
|
|
|
// decode is the generic switcher that decides which decoder to use for a field
|
|
func decode(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
// if depth has reached zero, we skip decoding entirely
|
|
if depth.depth == 0 || depth.skipDecode {
|
|
return dest, nil
|
|
}
|
|
depth.depth--
|
|
|
|
// obtain the handler from the map, checking for the more specific interfaces first
|
|
dec, ok := intfDecoders[dest.Type()]
|
|
if ok {
|
|
return dec(src, dest, prefix, depth)
|
|
}
|
|
|
|
dec, ok = kindDecoders[dest.Kind()]
|
|
if ok {
|
|
return dec(src, dest, prefix, depth)
|
|
}
|
|
|
|
logger.Debugf("Skipping unsupported field, interface: %T, kind %s", dest, dest.Kind())
|
|
return dest, nil
|
|
}
|
|
|
|
// decodeString is the degenerative case where what we get is what we need
|
|
func decodeString(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
v, err := src(prefix)
|
|
if err != nil {
|
|
logger.Debugf("No value found in data source for string at key %q", prefix)
|
|
return nilValue, err
|
|
}
|
|
|
|
return reflect.ValueOf(v), nil
|
|
}
|
|
|
|
// decodePrimitive wraps the fromString primitive decoding in a manner that can be called via decode
|
|
func decodePrimitive(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
var this reflect.Value
|
|
if !dest.CanAddr() {
|
|
logger.Debugf("Making new primitive for %s", prefix)
|
|
ptr := reflect.New(dest.Type())
|
|
this = ptr.Elem()
|
|
} else {
|
|
logger.Debugf("Reusing existing struct for %s", prefix)
|
|
this = dest
|
|
}
|
|
|
|
// see if there's a value to decode
|
|
v, err := src(prefix)
|
|
if err != nil {
|
|
logger.Debugf("No value available for key to primitive %s", prefix)
|
|
return nilValue, err
|
|
}
|
|
|
|
t := this.Type()
|
|
this.Set(fromString(reflect.Zero(t), v))
|
|
|
|
return this, nil
|
|
}
|
|
|
|
func decodePtr(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
// if we're not following pointers, then return immediately
|
|
if !depth.follow {
|
|
return dest, nil
|
|
}
|
|
|
|
// value representing the run-time data
|
|
logger.Debugf("Decoding pointer into object: %#v", dest)
|
|
|
|
// if the pointer is nil we need to create the destination type
|
|
target := dest
|
|
if dest.IsNil() {
|
|
target = reflect.New(dest.Type().Elem())
|
|
}
|
|
|
|
// check to see if the resulting object is not nil
|
|
// If it is nil, then there was nothing to decode and the pointer remains nil
|
|
result, err := decode(src, target.Elem(), prefix, depth)
|
|
logger.Debugf("target is now %#v, %+q ", target, target.Type())
|
|
if !result.IsValid() || err == ErrKeyNotFound {
|
|
// leave the pointer as nil if the result is zero type or invalid
|
|
return dest, nil
|
|
}
|
|
|
|
// neither pointer, nor zero
|
|
// NOTE: if the returned result is not addressable this can panic - that generally
|
|
// indicates an incorrect implementation of a decodeX method... those should always
|
|
// return addressable Values. See decodeByteSlice as an example - this uses make([]byte)
|
|
// rather than built in string(bytes) conversion specifically to get an addressable return
|
|
if dest.IsNil() {
|
|
dest = target
|
|
}
|
|
|
|
dest.Elem().Set(result)
|
|
|
|
return dest, nil
|
|
}
|
|
|
|
var typeType = reflect.TypeOf((*reflect.Type)(nil)).Elem()
|
|
|
|
func decodeStruct(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
// value representing the run-time data
|
|
logger.Debugf("Decoding struct into object: %#v, type: %s", dest, dest.Type().Name())
|
|
|
|
var this reflect.Value
|
|
if !dest.CanAddr() {
|
|
logger.Debugf("Making new struct for %s", prefix)
|
|
ptr := reflect.New(dest.Type())
|
|
this = ptr.Elem()
|
|
} else {
|
|
logger.Debugf("Reusing existing struct for %s", prefix)
|
|
this = dest
|
|
}
|
|
|
|
// do we have any data for this struct at all
|
|
var valid bool
|
|
var err error
|
|
noKeysFound := true
|
|
|
|
// iterate through every field in the struct
|
|
for i := 0; i < this.NumField(); i++ {
|
|
field := this.Field(i)
|
|
key, fdepth := calculateKeyFromField(this.Type().Field(i), prefix, depth)
|
|
if key == "" {
|
|
// this is either a malformed key or explicitly skipped
|
|
continue
|
|
}
|
|
|
|
// Dump what we have so far
|
|
logger.Debugf("Key: %s, Kind: %s Value: %s", key, field.Kind(), field.String())
|
|
|
|
// check to see if the resulting object is not nil
|
|
// If it is nil, then there was nothing to decode
|
|
var result reflect.Value
|
|
result, err = decode(src, field, key, fdepth)
|
|
if result.IsValid() {
|
|
logger.Debugf("Setting field %s to %#v", this.Type().Field(i).Name, result)
|
|
field.Set(result)
|
|
valid = true
|
|
if err != ErrKeyNotFound {
|
|
noKeysFound = false
|
|
}
|
|
} else {
|
|
logger.Debugf("Invalid result for field %s", this.Type().Field(i).Name)
|
|
}
|
|
}
|
|
|
|
if !valid || noKeysFound {
|
|
logger.Debugf("No valid result, returning nil value")
|
|
return nilValue, err
|
|
}
|
|
|
|
logger.Debugf("Return decoded structure for %s: %#v", prefix, this)
|
|
return this, nil
|
|
}
|
|
|
|
func decodeByteSlice(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
logger.Debugf("Converting string to []byte")
|
|
base, err := src(prefix)
|
|
if err != nil {
|
|
logger.Debugf("No value found in data source for []byte %q", prefix)
|
|
return nilValue, err
|
|
}
|
|
|
|
bytes, err := base64.StdEncoding.DecodeString(base)
|
|
if err != nil {
|
|
logger.Debugf("Expected base64 encoded string for []byte %q: %s", prefix, err)
|
|
return nilValue, err
|
|
}
|
|
|
|
length := len(bytes)
|
|
|
|
// we don't even try to merge byte arrays - no idea how to get append behaviour
|
|
// correct with reflection
|
|
// use make([]byte) rather than built in string(bytes) conversion to get an addressable return value
|
|
logger.Debugf("Making new slice for %s", prefix)
|
|
this := make([]byte, length, length)
|
|
|
|
copy(this, bytes)
|
|
|
|
return reflect.ValueOf(this), nil
|
|
}
|
|
|
|
func decodeSlice(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
// value representing the run-time data
|
|
logger.Debugf("Decoding struct into object: %#v", dest)
|
|
kind := dest.Type().Elem().Kind()
|
|
|
|
if kind == reflect.Uint8 {
|
|
return decodeByteSlice(src, dest, prefix, depth)
|
|
}
|
|
|
|
// do we have any data for this struct at all
|
|
length := 0
|
|
curLen := 0
|
|
|
|
// get the length of the array
|
|
len, err := src(prefix)
|
|
if err != nil || len == "" {
|
|
logger.Debugf("No value available for key %s - will create empty array if needed", prefix)
|
|
} else {
|
|
// if there's any data at all then we can assume we need to be extant
|
|
lengthValue := fromString(reflect.ValueOf(0), len)
|
|
length = int(lengthValue.Int()) + 1
|
|
}
|
|
|
|
var this reflect.Value
|
|
if !dest.IsValid() || dest.IsNil() || length > dest.Cap() {
|
|
logger.Debugf("Making new slice for %s", prefix)
|
|
this = reflect.MakeSlice(dest.Type(), length, length)
|
|
} else {
|
|
this = dest
|
|
this.SetLen(length)
|
|
curLen = this.Len()
|
|
}
|
|
|
|
// determine the key given the array type
|
|
if kind == reflect.Struct || isEncodableSliceElemType(dest.Type().Elem()) {
|
|
for i := 0; i < length; i++ {
|
|
// convert key to name|index format
|
|
key := appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
|
|
|
|
// if there's already a struct in the array at this index then we pass that as the current
|
|
// value
|
|
var cur reflect.Value
|
|
if i < curLen {
|
|
cur = this.Index(i)
|
|
} else {
|
|
cur = reflect.Zero(dest.Type().Elem())
|
|
}
|
|
|
|
var result reflect.Value
|
|
// #nosec: Errors unhandled.
|
|
result, _ = decode(src, cur, key, depth)
|
|
if result.IsValid() {
|
|
this.Index(i).Set(result)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
return this, nil
|
|
}
|
|
|
|
// convert key to name|index format
|
|
key := appendToPrefix(prefix, "", "~")
|
|
kval, err := src(key)
|
|
if err != nil {
|
|
logger.Debugf("No value found in data source for key %q", key)
|
|
return this, err
|
|
}
|
|
|
|
// lookup the key and split it
|
|
values := strings.Split(kval, Separator)
|
|
for i := 0; i < length; i++ {
|
|
v := values[i]
|
|
t := this.Type().Elem()
|
|
k := fromString(reflect.Zero(t), v)
|
|
// set the i'th slice item
|
|
this.Index(i).Set(k)
|
|
}
|
|
|
|
return this, nil
|
|
}
|
|
|
|
func decodeMap(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
// value representing the run-time data
|
|
logger.Debugf("Decoding struct into object: %#v", dest)
|
|
|
|
// if the value is the zero type, we have to create ourselves
|
|
var this reflect.Value
|
|
if !dest.IsValid() || dest.IsNil() {
|
|
logger.Debugf("Making new maps for %s", prefix)
|
|
this = reflect.MakeMap(dest.Type())
|
|
} else {
|
|
this = dest
|
|
}
|
|
|
|
mapkeys, err := src(prefix)
|
|
if mapkeys == "" || err != nil {
|
|
logger.Debugf("No value found in data source for maps keys %q", prefix)
|
|
return this, err
|
|
}
|
|
|
|
keytype := this.Type().Key()
|
|
valtype := this.Type().Elem()
|
|
|
|
// split the list of map keys and iterate
|
|
for _, value := range strings.Split(mapkeys, Separator) {
|
|
k := fromString(reflect.Zero(keytype), value)
|
|
target := this.MapIndex(k)
|
|
if !target.IsValid() {
|
|
target = reflect.Zero(valtype)
|
|
}
|
|
|
|
key := appendToPrefix(prefix, Separator, value)
|
|
|
|
// check to see if the resulting object is not nil
|
|
// If it is nil, then there was nothing to decode and the pointer remains nil
|
|
// #nosec: Errors unhandled.
|
|
result, _ := decode(src, target, key, depth)
|
|
if result.IsValid() {
|
|
this.SetMapIndex(k, result)
|
|
}
|
|
}
|
|
|
|
return this, nil
|
|
}
|
|
|
|
func decodeTime(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
|
v, err := src(prefix)
|
|
if err != nil {
|
|
logger.Debugf("No value found in data source for time %q", prefix)
|
|
return nilValue, err
|
|
}
|
|
|
|
t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", v)
|
|
if err != nil {
|
|
logger.Debugf("Failed to convert value %q to time", v)
|
|
}
|
|
|
|
return reflect.ValueOf(t), nil
|
|
}
|
|
|
|
// fromString converts string representation of a basic type to basic type
|
|
func fromString(field reflect.Value, value string) reflect.Value {
|
|
// handle the zero value
|
|
// TODO: can probably handle this more efficiently with a nil pointer return
|
|
// as whatever we're populating with primitives will already have their zero
|
|
// value.
|
|
if value == "" {
|
|
return reflect.Zero(field.Type())
|
|
}
|
|
|
|
switch field.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
s, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
logger.Errorf("Failed to convert value %#v (%s) to int: %s", value, field.Kind(), err.Error())
|
|
return field
|
|
}
|
|
return reflect.ValueOf(s).Convert(field.Type())
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
s, err := strconv.ParseUint(value, 10, 64)
|
|
if err != nil {
|
|
logger.Errorf("Failed to convert value %#v (%s) to uint: %s", value, field.Kind(), err.Error())
|
|
return field
|
|
}
|
|
return reflect.ValueOf(s).Convert(field.Type())
|
|
|
|
case reflect.Bool:
|
|
s, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
logger.Errorf("Failed to convert value %#v (%s) to bool: %s", value, field.Kind(), err.Error())
|
|
return field
|
|
}
|
|
return reflect.ValueOf(s)
|
|
|
|
case reflect.String:
|
|
return reflect.ValueOf(value)
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
s, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
logger.Errorf("Failed to convert value %#v (%s) to float: %s", value, field.Kind(), err.Error())
|
|
return field
|
|
}
|
|
return reflect.ValueOf(s)
|
|
|
|
}
|
|
logger.Debugf("Invalid Kind: %s (%#v)", field.Kind(), value)
|
|
|
|
return field
|
|
}
|
|
|
|
// DataSource provides a function that, give a key will return a value
|
|
// this is to be used during extraConfig decode to obtain values. Should
|
|
// return ErrKeyNotFound if the key does not exist in the data source.
|
|
type DataSource func(string) (string, error)
|
|
|
|
// Decode populates a destination with data from the supplied data source
|
|
func Decode(src DataSource, dest interface{}) interface{} {
|
|
if src == nil {
|
|
logger.Warnf("Decode source is nil - unable to continue")
|
|
return dest
|
|
}
|
|
|
|
// #nosec: Errors unhandled.
|
|
value, _ := decode(src, reflect.ValueOf(dest), "", Unbounded)
|
|
|
|
return value.Interface()
|
|
}
|
|
|
|
// DecodeWithPrefix populates a destination with data from the supplied data source, using
|
|
// the specified prefix - this allows for decode into substructres.
|
|
func DecodeWithPrefix(src DataSource, dest interface{}, prefix string) interface{} {
|
|
if src == nil {
|
|
logger.Warnf("Decode source is nil - unable to continue")
|
|
return dest
|
|
}
|
|
|
|
// #nosec: Errors unhandled.
|
|
value, _ := decode(src, reflect.ValueOf(dest), prefix, Unbounded)
|
|
|
|
return value.Interface()
|
|
}
|
|
|
|
// MapSource takes a key/value map and uses that as the datasource for decoding into
|
|
// target structures
|
|
func MapSource(src map[string]string) DataSource {
|
|
return func(key string) (string, error) {
|
|
val, ok := src[key]
|
|
if !ok {
|
|
return "", ErrKeyNotFound
|
|
}
|
|
return val, nil
|
|
}
|
|
}
|