3
0
corteza/pkg/settings/kv_decoder.go

137 lines
2.7 KiB
Go

package settings
import (
"errors"
"reflect"
"strings"
)
type (
// @todo support Decoder interface
// Decoder interface {
// Decode(kv KV, prefix string) error
// }
)
var (
// @todo support Decoder interface
// decoderTyEl = reflect.TypeOf((*Decoder)(nil)).Elem()
)
// Decode converts key-value (KV) into structs using tags & field names
//
// Supports decoding into all scalar types, can handle nested structures and simple maps (1 dim, string as key)
//
// Example:
// key-value pairs:
// string1: "string"
// number: 42
//
// struct{
// String1 string `kv:"string1`
// Number int
// }
//
func Decode(kv KV, dst interface{}, pp ...string) (err error) {
v := reflect.ValueOf(dst)
if v.Kind() != reflect.Ptr {
return errors.New("expecting a pointer, not a value")
}
if v.IsNil() {
return errors.New("nil pointer passed")
}
v = v.Elem()
var prefix string
if len(pp) > 0 {
// If called with prefix, join string slice + 1 empty string (to ensure tailing dot)
prefix = strings.Join(append(pp, ""), ".")
}
length := v.NumField()
for i := 0; i < length; i++ {
var (
f = v.Field(i)
t = v.Type().Field(i)
key = prefix + strings.ToLower(t.Name[:1]) + t.Name[1:]
tag = t.Tag.Get("kv")
tagFlags []string
)
if tag == "-" {
// Skip fields with kv tags equal to "-"
continue
}
// if !f.CanSet() {
// return errors.New("unexpected pointer for field " + t.Name)
// }
if tag != "" {
tagFlags = strings.Split(tag, ",")
if tagFlags[0] != "" {
// key explicitly set via tag, use that!
key = prefix + tagFlags[0]
}
for f := 1; f < len(tagFlags); f++ {
// @todo resolve i18n and other flags...
}
}
// @todo handle Decoder interface
// if f.Type().Implements(decoderTyEl) {
// result := reflect.ValueOf(&t).MethodByName("Decode").Call([]reflect.Value{
// reflect.ValueOf(kv.Filter(key)),
// reflect.ValueOf(prefix),
// })
//
// if len(result) != 1 {
// return errors.New("internal error, Decoder signature does not match")
// }
// }
// Handles structs
//
// It calls Decode recursively
if f.Kind() == reflect.Struct {
if err = Decode(kv.Filter(key), f.Addr().Interface(), key); err != nil {
return
}
continue
}
// Handles map values
if f.Kind() == reflect.Map {
if f.IsNil() {
// allocate new map
f.Set(reflect.MakeMap(f.Type()))
}
for k, val := range kv.CutPrefix(key + ".") {
mapValue := reflect.New(f.Type().Elem())
val.Unmarshal(mapValue.Interface())
f.SetMapIndex(reflect.ValueOf(k), mapValue.Elem())
}
continue
}
if val, ok := kv[key]; ok {
if err = val.Unmarshal(f.Addr().Interface()); err != nil {
return
}
}
}
return
}