From 2e742aa947859839c147a6722406dd0e9ab5f258 Mon Sep 17 00:00:00 2001 From: TeoV Date: Thu, 5 Sep 2019 17:54:38 +0300 Subject: [PATCH] Add ReflectFieldMethodInterface method in utils + tests for it --- config/dataprovider.go | 54 +++++++++++++++++++++++++++++++++++++++++ engine/account.go | 55 ------------------------------------------ engine/filters.go | 4 +-- utils/reflect.go | 51 +++++++++++++++++++++++++++++++++++++++ utils/reflect_test.go | 45 ++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 57 deletions(-) diff --git a/config/dataprovider.go b/config/dataprovider.go index adb0c79ef..351da2a65 100644 --- a/config/dataprovider.go +++ b/config/dataprovider.go @@ -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() +} diff --git a/engine/account.go b/engine/account.go index 857fe62ce..d71079607 100644 --- a/engine/account.go +++ b/engine/account.go @@ -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() -} diff --git a/engine/filters.go b/engine/filters.go index f965365bd..ccb0db6fe 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -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: diff --git a/utils/reflect.go b/utils/reflect.go index 9efb71bd5..4cfdf32aa 100644 --- a/utils/reflect.go +++ b/utils/reflect.go @@ -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 +} diff --git a/utils/reflect_test.go b/utils/reflect_test.go index 4a8ef07a7..f5bb50ff3 100644 --- a/utils/reflect_test.go +++ b/utils/reflect_test.go @@ -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) + } +}