micro/util/reflect/reflect.go
Vasiliy Tolstov 34f0b209cc codec: add ability to control codec via struct tags
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:20:39 +03:00

497 lines
12 KiB
Go

package reflect
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
)
var (
ErrInvalidStruct = errors.New("invalid struct specified")
ErrInvalidValue = errors.New("invalid value specified")
ErrNotFound = errors.New("struct field not found")
)
type Option func(*Options)
type Options struct {
Tags []string
SliceAppend bool
}
func Tags(t []string) Option {
return func(o *Options) {
o.Tags = t
}
}
func SliceAppend(b bool) Option {
return func(o *Options) {
o.SliceAppend = b
}
}
func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error {
var err error
var sval reflect.Value
var fname string
options := Options{}
for _, o := range opts {
o(&options)
}
dviface := reflect.ValueOf(dst)
if dviface.Kind() == reflect.Ptr {
dviface = dviface.Elem()
}
if dviface.Kind() != reflect.Struct {
return ErrInvalidStruct
}
dtype := dviface.Type()
for idx := 0; idx < dtype.NumField(); idx++ {
dfld := dtype.Field(idx)
dval := dviface.Field(idx)
if !dval.CanSet() || len(dfld.PkgPath) != 0 || !dval.IsValid() {
continue
}
fname = ""
for _, tname := range options.Tags {
tvalue, ok := dfld.Tag.Lookup(tname)
if !ok {
continue
}
tpart := strings.Split(tvalue, ",")
switch tname {
case "protobuf":
for _, p := range tpart {
if idx := strings.Index(p, "name="); idx > 0 {
fname = p[idx:]
}
}
default:
fname = tpart[0]
}
if fname != "" {
break
}
}
if fname == "" {
fname = FieldName(dfld.Name)
}
val, ok := mp[fname]
if !ok {
continue
}
sval = reflect.ValueOf(val)
switch getKind(dval) {
case reflect.Bool:
err = mergeBool(dval, sval)
case reflect.String:
err = mergeString(dval, sval)
case reflect.Int:
err = mergeInt(dval, sval)
case reflect.Uint:
err = mergeUint(dval, sval)
case reflect.Float32:
err = mergeFloat(dval, sval)
case reflect.Struct:
mp, ok := sval.Interface().(map[string]interface{})
if !ok {
return ErrInvalidValue
}
err = Merge(dval.Interface(), mp, opts...)
case reflect.Ptr:
mp, ok := sval.Interface().(map[string]interface{})
if !ok {
return ErrInvalidValue
}
if dval.IsNil() {
dval.Set(reflect.New(dval.Type().Elem()))
if dval.Elem().Type().Kind() == reflect.Struct {
for i := 0; i < dval.Elem().NumField(); i++ {
field := dval.Elem().Field(i)
if field.Type().Kind() == reflect.Ptr && field.IsNil() && dval.Elem().Type().Field(i).Anonymous {
field.Set(reflect.New(field.Type().Elem()))
}
}
}
}
err = Merge(dval.Interface(), mp, opts...)
case reflect.Slice:
if !options.SliceAppend && dval.IsNil() {
dval.Set(reflect.MakeSlice(dval.Type(), sval.Len(), sval.Len()))
} else if options.SliceAppend && dval.IsNil() {
dval.Set(reflect.MakeSlice(dval.Type(), 0, 0))
}
for idx := 0; idx < sval.Len(); idx++ {
var idval reflect.Value
if !options.SliceAppend {
idval = dval.Index(idx)
} else {
idval = reflect.Indirect(reflect.New(dval.Type().Elem()))
}
if getKind(idval) == reflect.Ptr && idval.IsNil() {
idval.Set(reflect.New(idval.Type().Elem()))
if idval.Elem().Type().Kind() == reflect.Struct {
for i := 0; i < idval.Elem().NumField(); i++ {
field := idval.Elem().Field(i)
if field.Type().Kind() == reflect.Ptr && field.IsNil() && idval.Elem().Type().Field(i).Anonymous {
field.Set(reflect.New(field.Type().Elem()))
}
}
}
}
switch getKind(idval) {
case reflect.Bool:
err = mergeBool(idval, sval.Index(idx))
case reflect.String:
err = mergeString(idval, sval.Index(idx))
case reflect.Int:
err = mergeInt(idval, sval.Index(idx))
case reflect.Uint:
err = mergeUint(idval, sval.Index(idx))
case reflect.Float32:
err = mergeFloat(idval, sval.Index(idx))
case reflect.Struct:
imp, ok := sval.Index(idx).Interface().(map[string]interface{})
if !ok {
return ErrInvalidValue
}
err = Merge(idval.Interface(), imp, opts...)
case reflect.Ptr:
nsval := sval.Index(idx)
if getKind(sval.Index(idx)) == reflect.Interface {
nsval = reflect.ValueOf(nsval.Interface())
}
switch reflect.Indirect(idval).Type().String() {
case "wrapperspb.BoolValue":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeBool(eva, nsval)
}
case "wrapperspb.BytesValue":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeUint(eva, nsval)
}
case "wrapperspb.DoubleValue", "wrapperspb.FloatValue":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeFloat(eva, nsval)
}
case "wrapperspb.Int32Value", "wrapperspb.Int64Value":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeInt(eva, nsval)
}
case "wrapperspb.StringValue":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeString(eva, nsval)
}
case "wrapperspb.UInt32Value", "wrapperspb.UInt64Value":
if eva := reflect.Indirect(idval).FieldByName("Value"); eva.IsValid() {
err = mergeUint(eva, nsval)
}
default:
imp, ok := nsval.Interface().(map[string]interface{})
if !ok {
return ErrInvalidValue
}
if idval.IsNil() {
idval.Set(reflect.New(idval.Type().Elem()))
if idval.Elem().Type().Kind() == reflect.Struct {
for i := 0; i < idval.Elem().NumField(); i++ {
field := idval.Elem().Field(i)
if field.Type().Kind() == reflect.Ptr && field.IsNil() && idval.Elem().Type().Field(i).Anonymous {
field.Set(reflect.New(field.Type().Elem()))
}
}
}
}
err = Merge(idval.Interface(), imp, opts...)
}
}
if options.SliceAppend {
dval.Set(reflect.Append(dval, idval))
}
}
/*
case reflect.Interface:
err = d.decodeBasic(name, input, outVal)
case reflect.Map:
err = merge(dval, sval)
case reflect.Array:
err = mergeArray(dval, sval)
*/
default:
err = ErrInvalidValue
}
if err != nil {
err = fmt.Errorf("%v key %v invalid val %v", err, fname, sval.Interface())
}
}
return nil
}
func mergeBool(dval reflect.Value, sval reflect.Value) error {
switch getKind(sval) {
case reflect.Int:
switch sval.Int() {
case 1:
dval.SetBool(true)
case 0:
dval.SetBool(false)
default:
return ErrInvalidValue
}
case reflect.Uint:
switch sval.Uint() {
case 1:
dval.SetBool(true)
case 0:
dval.SetBool(false)
default:
return ErrInvalidValue
}
case reflect.Float32:
switch sval.Float() {
case 1:
dval.SetBool(true)
case 0:
dval.SetBool(false)
default:
return ErrInvalidValue
}
case reflect.Bool:
dval.SetBool(sval.Bool())
case reflect.String:
switch sval.String() {
case "t", "T", "true", "TRUE", "True", "1", "yes":
dval.SetBool(true)
case "f", "F", "false", "FALSE", "False", "0", "no":
dval.SetBool(false)
default:
return ErrInvalidValue
}
case reflect.Interface:
return mergeBool(dval, reflect.ValueOf(fmt.Sprintf("%v", sval.Interface())))
default:
return ErrInvalidValue
}
return nil
}
func mergeString(dval reflect.Value, sval reflect.Value) error {
switch getKind(sval) {
case reflect.Int:
dval.SetString(strconv.FormatInt(sval.Int(), sval.Type().Bits()))
case reflect.Uint:
dval.SetString(strconv.FormatUint(sval.Uint(), sval.Type().Bits()))
case reflect.Float32:
dval.SetString(strconv.FormatFloat(sval.Float(), 'f', -1, sval.Type().Bits()))
case reflect.Bool:
switch sval.Bool() {
case true:
dval.SetString(strconv.FormatBool(true))
case false:
dval.SetString(strconv.FormatBool(false))
}
case reflect.String:
dval.SetString(sval.String())
case reflect.Interface:
return mergeString(dval, reflect.ValueOf(fmt.Sprintf("%v", sval.Interface())))
default:
return ErrInvalidValue
}
return nil
}
func mergeInt(dval reflect.Value, sval reflect.Value) error {
switch getKind(sval) {
case reflect.Int:
dval.SetInt(sval.Int())
case reflect.Uint:
dval.SetInt(int64(sval.Uint()))
case reflect.Float32:
dval.SetInt(int64(sval.Float()))
case reflect.Bool:
switch sval.Bool() {
case true:
dval.SetInt(1)
case false:
dval.SetInt(0)
}
case reflect.String:
l, err := strconv.ParseInt(sval.String(), 0, dval.Type().Bits())
if err != nil {
return err
}
dval.SetInt(l)
case reflect.Interface:
return mergeInt(dval, reflect.ValueOf(fmt.Sprintf("%v", sval.Interface())))
default:
return ErrInvalidValue
}
return nil
}
func mergeUint(dval reflect.Value, sval reflect.Value) error {
switch getKind(sval) {
case reflect.Int:
dval.SetUint(uint64(sval.Int()))
case reflect.Uint:
dval.SetUint(sval.Uint())
case reflect.Float32:
dval.SetUint(uint64(sval.Float()))
case reflect.Bool:
switch sval.Bool() {
case true:
dval.SetUint(1)
case false:
dval.SetUint(0)
}
case reflect.String:
l, err := strconv.ParseUint(sval.String(), 0, dval.Type().Bits())
if err != nil {
return err
}
dval.SetUint(l)
case reflect.Interface:
return mergeUint(dval, reflect.ValueOf(fmt.Sprintf("%v", sval.Interface())))
default:
return ErrInvalidValue
}
return nil
}
func mergeFloat(dval reflect.Value, sval reflect.Value) error {
switch getKind(sval) {
case reflect.Int:
dval.SetFloat(float64(sval.Int()))
case reflect.Uint:
dval.SetFloat(float64(sval.Uint()))
case reflect.Float32:
dval.SetFloat(sval.Float())
case reflect.Bool:
switch sval.Bool() {
case true:
dval.SetFloat(1)
case false:
dval.SetFloat(0)
}
case reflect.String:
l, err := strconv.ParseFloat(sval.String(), dval.Type().Bits())
if err != nil {
return err
}
dval.SetFloat(l)
case reflect.Interface:
return mergeFloat(dval, reflect.ValueOf(fmt.Sprintf("%v", sval.Interface())))
default:
return ErrInvalidValue
}
return nil
}
func getKind(val reflect.Value) reflect.Kind {
kind := val.Kind()
switch {
case kind >= reflect.Int && kind <= reflect.Int64:
return reflect.Int
case kind >= reflect.Uint && kind <= reflect.Uint64:
return reflect.Uint
case kind >= reflect.Float32 && kind <= reflect.Float64:
return reflect.Float32
}
return kind
}
// IsZero returns true if struct is zero (not have any defined values)
func IsZero(src interface{}) bool {
v := reflect.ValueOf(src)
return IsEmpty(v)
}
// Zero creates new zero interface
func Zero(src interface{}) (interface{}, error) {
sv := reflect.ValueOf(src)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() == reflect.Invalid {
return nil, ErrInvalidStruct
}
dst := reflect.New(sv.Type())
return dst.Interface(), nil
}
// IsEmpty returns true if value empty
func IsEmpty(v reflect.Value) bool {
switch getKind(v) {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int:
return v.Int() == 0
case reflect.Uint:
return v.Uint() == 0
case reflect.Float32:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return true
}
return IsEmpty(v.Elem())
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
case reflect.Struct:
var ok bool
for i := 0; i < v.NumField(); i++ {
ok = IsEmpty(v.FieldByIndex([]int{i}))
if !ok {
return false
}
}
default:
return false
}
return true
}
func FieldName(name string) string {
newstr := make([]rune, 0, len(name))
for idx, chr := range name {
if idx == 0 {
newstr = append(newstr, unicode.ToLower(chr))
continue
} else if unicode.IsUpper(chr) {
newstr = append(newstr, '_')
newstr = append(newstr, unicode.ToLower(chr))
continue
}
newstr = append(newstr, chr)
}
return string(newstr)
}