Updated NavigableMap FieldAsInterface

This commit is contained in:
Tripon Alexandru-Ionut
2019-05-14 15:29:13 +03:00
committed by Dan Christian Bogos
parent d51fdaf90a
commit 1db551c932
5 changed files with 316 additions and 42 deletions

View File

@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"time"
@@ -101,54 +102,125 @@ func (nM *NavigableMap) FieldAsInterface(fldPath []string) (fldVal interface{},
return nil, errors.New("empty field path")
}
lastMp := nM.data // last map when layered
var canCast bool
for i, spath := range fldPath {
if i == lenPath-1 { // lastElement
var idx *int
if idxStart := strings.Index(spath, utils.IdxStart); idxStart != -1 &&
strings.HasSuffix(spath, utils.IdxEnd) {
slctr := spath[idxStart+1 : len(spath)-1]
if !strings.HasPrefix(slctr, utils.DynamicDataPrefix) {
if idxVal, err := strconv.Atoi(slctr); err != nil {
return nil, err
} else {
idx = utils.IntPointer(idxVal)
}
}
spath = spath[:idxStart] // ignore the selector for now since it is processed in other places
}
var has bool
fldVal, has = lastMp[spath]
if !has {
return nil, utils.ErrNotFound
}
if valItms, isItms := fldVal.([]*NMItem); isItms && idx != nil {
if *idx >= len(valItms) {
return nil, fmt.Errorf("selector index %d out of range", *idx)
}
fldVal = valItms[*idx].Data
}
return nM.getLastItem(lastMp, spath)
}
if lastMp, err = nM.getNextMap(lastMp, spath); err != nil {
return
}
elmnt, has := lastMp[spath]
if !has {
return nil, utils.ErrNotFound
}
lastMp, canCast = elmnt.(map[string]interface{})
if !canCast {
lastMpNM, canCast := elmnt.(*NavigableMap) // attempt to cast into NavigableMap
if !canCast {
err = fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
elmnt, elmnt, spath)
return
}
lastMp = lastMpNM.data
}
}
err = errors.New("end of function")
return
}
// getLastItem returns the item from the map
// checking if it needs to return the item or an element of him if the item is a slice
func (nM *NavigableMap) getLastItem(mp map[string]interface{}, spath string) (val interface{}, err error) {
var idx *int
spath, idx = nM.getIndex(spath)
var has bool
val, has = mp[spath]
if !has {
return nil, utils.ErrNotFound
}
if idx == nil {
return val, nil
}
switch vt := val.(type) {
case []string:
if *idx > len(vt) {
return nil, utils.ErrNotFound
// return nil, fmt.Errorf("selector index %d out of range", *idx)
}
return vt[*idx], nil
case []*NMItem:
if *idx > len(vt) {
return nil, utils.ErrNotFound
// return nil, fmt.Errorf("selector index %d out of range", *idx)
}
return vt[*idx].Data, nil
default:
}
// only if all above fails use reflect:
vr := reflect.ValueOf(val)
if vr.Kind() == reflect.Ptr {
vr = vr.Elem()
}
if vr.Kind() != reflect.Slice && vr.Kind() != reflect.Array {
return nil, utils.ErrNotFound
// return nil, fmt.Errorf("selector index used on non slice type(%T)", val)
}
if *idx > vr.Len() {
return nil, utils.ErrNotFound
// return nil, fmt.Errorf("selector index %d out of range", *idx)
}
return vr.Index(*idx).Interface(), nil
}
// getNextMap returns the next map from the given map
// used only for path parsing
func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (map[string]interface{}, error) {
var idx *int
spath, idx = nM.getIndex(spath)
mi, has := mp[spath]
if !has {
return nil, utils.ErrNotFound
}
if idx == nil {
switch mv := mi.(type) {
case map[string]interface{}:
return mv, nil
case *map[string]interface{}:
return *mv, nil
case NavigableMap:
return mv.data, nil
case *NavigableMap:
return mv.data, nil
default:
}
} else {
switch mv := mi.(type) {
case []map[string]interface{}:
if *idx < len(mv) {
return mv[*idx], nil
}
case []NavigableMap:
if *idx < len(mv) {
return mv[*idx].data, nil
}
case []*NavigableMap:
if *idx < len(mv) {
return mv[*idx].data, nil
}
default:
}
return nil, utils.ErrNotFound // xml compatible
}
return nil, fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
mi, mi, spath)
}
// getIndex returns the path and index if index present
// path[index]=>path,index
// path=>path,nil
func (nM *NavigableMap) getIndex(spath string) (opath string, idx *int) {
idxStart := strings.Index(spath, utils.IdxStart)
if idxStart == -1 || !strings.HasSuffix(spath, utils.IdxEnd) {
return spath, nil
}
slctr := spath[idxStart+1 : len(spath)-1]
opath = spath[:idxStart]
if strings.HasPrefix(slctr, utils.DynamicDataPrefix) {
return
}
idxVal, err := strconv.Atoi(slctr)
if err != nil {
return spath, nil
}
return opath, &idxVal
}
// FieldAsString returns the field value as string for the path specified
// implements DataProvider
func (nM *NavigableMap) FieldAsString(fldPath []string) (fldVal string, err error) {

View File

@@ -19,6 +19,7 @@ package config
import (
"encoding/xml"
"fmt"
"reflect"
"strings"
"testing"
@@ -873,3 +874,185 @@ func TestNavMapMerge(t *testing.T) {
t.Errorf("expecting: %+v, received: %+v", nM2, nM)
}
}
func TestNavMapGetIndex(t *testing.T) {
nM := NewNavigableMap(nil)
var expIndx *int
var expPath string
var path string = "Fld1"
expPath = path
if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx {
t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx)
}
path = "slice[5]"
expPath = "slice"
expIndx = utils.IntPointer(5)
if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx {
t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx)
}
path = "slice[~cgreq.Count]"
expPath = "slice"
expIndx = nil
if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx {
t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx)
}
}
func TestNavMapGetNextMap(t *testing.T) {
nM := NewNavigableMap(nil)
mp := map[string]interface{}{
"field1": 10,
"field2": []string{"val1", "val2"},
"field3": []int{1, 2, 3},
"map1": map[string]interface{}{"field1": 100000},
"map2": []map[string]interface{}{map[string]interface{}{"field2": 11}},
"map3": []NavigableMap{NavigableMap{data: map[string]interface{}{"field4": 112}}},
}
path := "map4"
if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() {
t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err)
}
path = "map2[10]"
if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() {
t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err)
}
path = "field1"
experr := fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
mp[path], mp[path], path)
if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != experr.Error() {
t.Errorf("Expected error: %s , received error %v", experr.Error(), err)
}
path = "map1"
expMap := map[string]interface{}{"field1": 100000}
if rm, err := nM.getNextMap(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rm, expMap) {
t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm))
}
path = "map2[0]"
expMap = map[string]interface{}{"field2": 11}
if rm, err := nM.getNextMap(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rm, expMap) {
t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm))
}
path = "map3[0]"
expMap = map[string]interface{}{"field4": 112}
if rm, err := nM.getNextMap(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rm, expMap) {
t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm))
}
}
func TestNavMapgetLastItem(t *testing.T) {
nM := NewNavigableMap(nil)
mp := map[string]interface{}{
"field1": 10,
"field2": []string{"val1", "val2"},
"field3": []int{1, 2, 3},
"map1": map[string]interface{}{"field1": 100000},
"map2": []map[string]interface{}{map[string]interface{}{"field2": 11}},
"map3": []NavigableMap{NavigableMap{data: map[string]interface{}{"field4": 112}}},
}
path := "map4"
if _, err := nM.getLastItem(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() {
t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err)
}
path = "map2[10]"
if _, err := nM.getLastItem(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() {
t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err)
}
path = "field1"
var expVal interface{} = 10
if rplyVal, err := nM.getLastItem(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expVal, rplyVal) {
t.Errorf("Expected: %v ,received: %v", expVal, rplyVal)
}
path = "field2[1]"
expVal = "val2"
if rplyVal, err := nM.getLastItem(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expVal, rplyVal) {
t.Errorf("Expected: %v ,received: %v", expVal, rplyVal)
}
path = "field3[2]"
expVal = 3
if rplyVal, err := nM.getLastItem(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expVal, rplyVal) {
t.Errorf("Expected: %v ,received: %v", expVal, rplyVal)
}
path = "field2"
expVal = []string{"val1", "val2"}
if rplyVal, err := nM.getLastItem(mp, path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expVal, rplyVal) {
t.Errorf("Expected: %v ,received: %v", expVal, rplyVal)
}
}
func TestNavMapFieldAsInterface(t *testing.T) {
nM := &NavigableMap{
data: map[string]interface{}{
"FirstLevel": map[string]interface{}{
"SecondLevel": []map[string]interface{}{
map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": "Val1",
},
},
map[string]interface{}{
"Count": 10,
"ThirdLevel2": map[string]interface{}{
"Fld2": []string{"Val1", "Val2", "Val3"},
},
},
},
},
"AnotherFirstLevel": "ValAnotherFirstLevel",
},
}
path := []string{"FirstLevel", "SecondLevel[0]", "Count"}
expErr := utils.ErrNotFound
var eVal interface{} = nil
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
}
path = []string{"AnotherFirstLevel", "SecondLevel", "Count"}
expErr = fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
nM.data["AnotherFirstLevel"], nM.data["AnotherFirstLevel"], "AnotherFirstLevel")
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
}
path = []string{"FirstLevel", "SecondLevel[1]", "Count"}
eVal = 10
if rplyVal, err := nM.FieldAsInterface(path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eVal, rplyVal) {
t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal))
}
path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2"}
eVal = []string{"Val1", "Val2", "Val3"}
if rplyVal, err := nM.FieldAsInterface(path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eVal, rplyVal) {
t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal))
}
path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2[2]"}
eVal = "Val3"
if rplyVal, err := nM.FieldAsInterface(path); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eVal, rplyVal) {
t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal))
}
}

View File

@@ -23,6 +23,7 @@ import (
"sort"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -148,6 +149,24 @@ func (sSpls *SortedSuppliers) Digest() string {
return strings.Join(sSpls.SuppliersWithParams(), utils.FIELDS_SEP)
}
func (sSpls *SortedSuppliers) AsNavigableMap() (nm *config.NavigableMap) {
mp := map[string]interface{}{
"ProfileID": sSpls.ProfileID,
"Sorting": sSpls.Sorting,
"Count": sSpls.Count,
}
sm := make([]map[string]interface{}, len(sSpls.SortedSuppliers))
for i, ss := range sSpls.SortedSuppliers {
sm[i] = map[string]interface{}{
"SupplierID": ss.SupplierID,
"SupplierParameters": ss.SupplierParameters,
"SortingData": ss.SortingData,
}
}
mp["SortedSuppliers"] = sm
return config.NewNavigableMap(mp)
}
type SupplierWithParams struct {
SupplierName string
SupplierParams string

View File

@@ -1714,7 +1714,7 @@ func (v1AuthReply *V1AuthorizeReply) AsNavigableMap(
cgrReply[utils.CapMaxUsage] = *v1AuthReply.MaxUsage
}
if v1AuthReply.Suppliers != nil {
cgrReply[utils.CapSuppliers] = *v1AuthReply.Suppliers
cgrReply[utils.CapSuppliers] = v1AuthReply.Suppliers.AsNavigableMap()
}
if v1AuthReply.ThresholdIDs != nil {
cgrReply[utils.CapThresholds] = *v1AuthReply.ThresholdIDs
@@ -2604,7 +2604,7 @@ func (v1Rply *V1ProcessEventReply) AsNavigableMap(
cgrReply[utils.CapAttributes] = attrs
}
if v1Rply.Suppliers != nil {
cgrReply[utils.CapSuppliers] = *v1Rply.Suppliers
cgrReply[utils.CapSuppliers] = v1Rply.Suppliers.AsNavigableMap()
}
}
return config.NewNavigableMap(cgrReply), nil

View File

@@ -1007,7 +1007,7 @@ func TestSessionSV1AuthorizeReplyAsNavigableMap(t *testing.T) {
utils.CapAttributes: map[string]interface{}{"OfficeGroup": "Marketing"},
utils.CapResourceAllocation: "ResGr1",
utils.CapMaxUsage: 5 * time.Minute,
utils.CapSuppliers: *splrs,
utils.CapSuppliers: splrs.AsNavigableMap(),
utils.CapThresholds: *thIDs,
utils.CapStatQueues: *statIDs,
})