Add ReflectFieldMethodInterface method in utils + tests for it

This commit is contained in:
TeoV
2019-09-05 17:54:38 +03:00
committed by Dan Christian Bogos
parent 373801e5b1
commit 2e742aa947
5 changed files with 152 additions and 57 deletions

View File

@@ -49,3 +49,57 @@ func GetDynamicString(dnVal string, dP DataProvider) (string, error) {
}
return dnVal, nil
}
//NewObjectDP constructs a DataProvider
func NewObjectDP(obj interface{}) (dP DataProvider) {
dP = &ObjectDP{obj: obj, cache: NewNavigableMap(nil)}
return
}
type ObjectDP struct {
obj interface{}
cache *NavigableMap
}
// String is part of engine.DataProvider interface
// when called, it will display the already parsed values out of cache
func (objDP *ObjectDP) String() string {
return utils.ToJSON(objDP.obj)
}
// FieldAsInterface is part of engine.DataProvider interface
func (objDP *ObjectDP) FieldAsInterface(fldPath []string) (data interface{}, err error) {
// []string{ BalanceMap *monetary[0] Value }
if data, err = objDP.cache.FieldAsInterface(fldPath); err == nil ||
err != utils.ErrNotFound { // item found in cache
return
}
err = nil // cancel previous err
// for _, fld := range fldPath {
// //process each field
// }
objDP.cache.Set(fldPath, data, false, false)
return
}
// FieldAsString is part of engine.DataProvider interface
func (objDP *ObjectDP) FieldAsString(fldPath []string) (data string, err error) {
var valIface interface{}
valIface, err = objDP.FieldAsInterface(fldPath)
if err != nil {
return
}
return utils.IfaceAsString(valIface), nil
}
// AsNavigableMap is part of engine.DataProvider interface
func (objDP *ObjectDP) AsNavigableMap([]*FCTemplate) (
nm *NavigableMap, err error) {
return nil, utils.ErrNotImplemented
}
// RemoteHost is part of engine.DataProvider interface
func (objDP *ObjectDP) RemoteHost() net.Addr {
return utils.LocalAddr()
}

View File

@@ -22,7 +22,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"strings"
"time"
@@ -1172,57 +1171,3 @@ func (as *AccountSummary) Clone() (cln *AccountSummary) {
}
return
}
// newAccountCache constructs a DataProvider
func NewAccountDP(account *Account) (dP config.DataProvider) {
dP = &AccountDP{account: account, cache: config.NewNavigableMap(nil)}
return
}
// AccountCache implements engine.DataProvider so we can pass it to filters
type AccountDP struct {
account *Account
cache *config.NavigableMap
}
// String is part of engine.DataProvider interface
// when called, it will display the already parsed values out of cache
func (accDP *AccountDP) String() string {
return utils.ToJSON(accDP.account)
}
// FieldAsInterface is part of engine.DataProvider interface
func (accDP *AccountDP) FieldAsInterface(fldPath []string) (data interface{}, err error) {
if len(fldPath) != 1 {
return nil, utils.ErrNotFound
}
if data, err = accDP.cache.FieldAsInterface(fldPath); err == nil ||
err != utils.ErrNotFound { // item found in cache
return
}
err = nil // cancel previous err
//check the field in the account
accDP.cache.Set(fldPath, data, false, false)
return
}
// FieldAsString is part of engine.DataProvider interface
func (accDP *AccountDP) FieldAsString(fldPath []string) (data string, err error) {
var valIface interface{}
valIface, err = accDP.FieldAsInterface(fldPath)
if err != nil {
return
}
return utils.IfaceAsString(valIface), nil
}
// AsNavigableMap is part of engine.DataProvider interface
func (accDP *AccountDP) AsNavigableMap([]*config.FCTemplate) (
nm *config.NavigableMap, err error) {
return nil, utils.ErrNotImplemented
}
// RemoteHost is part of engine.DataProvider interface
func (accDP *AccountDP) RemoteHost() net.Addr {
return utils.LocalAddr()
}

View File

@@ -664,7 +664,7 @@ func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, field
&utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
return
}
dp = NewAccountDP(account)
dp = config.NewObjectDP(account)
case strings.HasPrefix(fieldName, utils.MetaResources):
case strings.HasPrefix(fieldName, utils.MetaStats):
default:
@@ -684,7 +684,7 @@ func (fS *FilterS) getFieldValueDataProviders(initialDP config.DataProvider, val
&utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
return
}
dp[i] = NewAccountDP(account)
dp[i] = config.NewObjectDP(account)
case strings.HasPrefix(val, utils.MetaResources):
case strings.HasPrefix(val, utils.MetaStats):
default:

View File

@@ -542,3 +542,54 @@ func Difference(items ...interface{}) (diff interface{}, err error) {
}
return
}
// ReflectFieldMethodInterface parses intf attepting to return the field value or error otherwise
// Supports "ExtraFields" where additional fields are dynamically inserted in map with field name: extraFieldsLabel
func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interface{}, err error) {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var field reflect.Value
switch v.Kind() {
case reflect.Struct:
field = v.FieldByName(fldName)
case reflect.Map:
field = v.MapIndex(reflect.ValueOf(fldName))
case reflect.Slice, reflect.Array:
//convert fldName to int
idx, err := strconv.Atoi(fldName)
if err != nil {
return nil, err
}
if idx > v.Len() {
return nil, fmt.Errorf("out of range")
}
field = v.Index(idx)
default:
return nil, fmt.Errorf("unsupported field kind: %v", v.Kind())
}
if !field.IsValid() {
// handle function with pointer
v = reflect.ValueOf(obj)
field = v.MethodByName(fldName)
if !field.IsValid() {
return nil, ErrNotFound
} else {
if field.Type().NumIn() != 0 {
return nil, fmt.Errorf("invalid function called")
}
if field.Type().NumOut() > 2 {
return nil, fmt.Errorf("invalid function called")
}
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
if !field.Type().Out(1).Implements(errorInterface) {
return nil, fmt.Errorf("invalid function called")
}
fields := field.Call([]reflect.Value{})
//verify if error is not nil
return fields[0].Interface(), nil
}
}
return field.Interface(), nil
}

View File

@@ -694,3 +694,48 @@ func TestEqualTo(t *testing.T) {
t.Error("should be equal")
}
}
type TestA struct {
StrField string
}
func (_ *TestA) TestFunc() string {
return "This is a test function on a structure"
}
func (_ *TestA) TestFuncWithParam(param string) string {
return "Invalid"
}
func (_ *TestA) TestFuncWithError() (string, error) {
return "TestFunction", nil
}
func (_ *TestA) TestFuncWithError2() (string, error) {
return "TestFunction", ErrPartiallyExecuted
}
func TestReflectFieldMethodInterface(t *testing.T) {
a := &TestA{StrField: "TestStructField"}
ifValue, err := ReflectFieldMethodInterface(a, "StrField")
if err != nil {
t.Error(err)
} else if ifValue != "TestStructField" {
t.Errorf("Expecting: TestStructField, received: %+v", ifValue)
}
ifValue, err = ReflectFieldMethodInterface(a, "InexistentField")
if err != ErrNotFound {
t.Error(err)
}
ifValue, err = ReflectFieldMethodInterface(a, "TestFunc")
if err != nil {
t.Error(err)
} else if ifValue != "This is a test function on a structure" {
t.Errorf("Expecting: This is a test function on a structure, received: %+v", ifValue)
}
ifValue, err = ReflectFieldMethodInterface(a, "TestFuncWithError")
if err != nil {
t.Error(err)
} else if ifValue != "TestFunction" {
t.Errorf("Expecting: TestFunction, received: %+v", ifValue)
}
}