154 lines
3.4 KiB
Go
154 lines
3.4 KiB
Go
package settings
|
|
|
|
import (
|
|
"github.com/pkg/errors"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
// KVDecoder interface for custom decoding logic
|
|
KVDecoder interface {
|
|
DecodeKV(KV, string) error
|
|
}
|
|
)
|
|
|
|
// DecodeKV 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 DecodeKV(kv KV, dst interface{}, pp ...string) (err error) {
|
|
valueOf := reflect.ValueOf(dst)
|
|
if valueOf.Kind() != reflect.Ptr {
|
|
return errors.New("expecting a pointer, not a value")
|
|
}
|
|
|
|
if valueOf.IsNil() {
|
|
return errors.New("nil pointer passed")
|
|
}
|
|
|
|
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, ""), ".")
|
|
}
|
|
|
|
valueOf = valueOf.Elem()
|
|
|
|
length := valueOf.NumField()
|
|
|
|
for i := 0; i < length; i++ {
|
|
var (
|
|
structField = valueOf.Field(i)
|
|
tags = make(map[string]bool)
|
|
)
|
|
|
|
if !structField.CanSet() {
|
|
continue
|
|
}
|
|
|
|
var (
|
|
structFType = valueOf.Type().Field(i)
|
|
|
|
// whe nwe use name of the struct field directly, remove upper-case from first letter
|
|
key = prefix + strings.ToLower(structFType.Name[:1]) + structFType.Name[1:]
|
|
|
|
tag = structFType.Tag.Get("kv")
|
|
|
|
tagFlags []string
|
|
)
|
|
|
|
if tag == "-" {
|
|
// Skip fields with kv tags equal to "-"
|
|
continue
|
|
}
|
|
|
|
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...
|
|
tags[tagFlags[f]] = true
|
|
}
|
|
}
|
|
|
|
var structValue interface{}
|
|
|
|
if structField.Kind() == reflect.Ptr {
|
|
structValue = structField.Interface()
|
|
} else {
|
|
structValue = structField.Addr().Interface()
|
|
}
|
|
|
|
// Handle custom KVDecoder
|
|
if decodeMethod := reflect.ValueOf(structValue).MethodByName("DecodeKV"); decodeMethod.IsValid() {
|
|
if decode, ok := decodeMethod.Interface().(func(KV, string) error); !ok {
|
|
panic("invalid DecodeKV() function signature")
|
|
} else if err = decode(kv, key); err != nil {
|
|
return errors.Wrapf(err, "cannot decode settings for %q", key)
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if !tags["final"] {
|
|
// Handles structs
|
|
//
|
|
// It calls DecodeKV recursively
|
|
if structField.Kind() == reflect.Struct {
|
|
if err = DecodeKV(kv.Filter(key), structValue, key); err != nil {
|
|
return
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Handles map values
|
|
if structField.Kind() == reflect.Map {
|
|
if structField.IsNil() {
|
|
// allocate new map
|
|
structField.Set(reflect.MakeMap(structField.Type()))
|
|
}
|
|
|
|
// cut KV key prefix and use the rest for the map key
|
|
for k, val := range kv.CutPrefix(key + ".") {
|
|
mapValue := reflect.New(structField.Type().Elem())
|
|
err = val.Unmarshal(mapValue.Interface())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "cannot decode settings for %q", key)
|
|
}
|
|
|
|
structField.SetMapIndex(reflect.ValueOf(k), mapValue.Elem())
|
|
}
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Native type
|
|
if val, ok := kv[key]; ok {
|
|
// Always use pointer to value
|
|
if err = val.Unmarshal(structField.Addr().Interface()); err != nil {
|
|
return errors.Wrapf(err, "cannot decode settings for %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|