mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Added DynamicDataProvider
This commit is contained in:
committed by
Dan Christian Bogos
parent
c5b472c93a
commit
da9344128c
@@ -1943,7 +1943,7 @@ func TestProcessAttributeVariable(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "Val2"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_VARIABLE"},
|
||||
@@ -2001,7 +2001,7 @@ func TestProcessAttributeComposed(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "Val2Concatenated"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_COMPOSED"},
|
||||
@@ -2054,7 +2054,7 @@ func TestProcessAttributeUsageDifference(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "1h0m0s"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_USAGE_DIFF"},
|
||||
@@ -2107,7 +2107,7 @@ func TestProcessAttributeSum(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "16"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_SUM"},
|
||||
@@ -2160,7 +2160,7 @@ func TestProcessAttributeDiff(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "39"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_DIFF"},
|
||||
@@ -2213,7 +2213,7 @@ func TestProcessAttributeMultiply(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "2750"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_MULTIPLY"},
|
||||
@@ -2266,7 +2266,7 @@ func TestProcessAttributeDivide(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "2.75"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_DIVIDE"},
|
||||
@@ -2319,7 +2319,7 @@ func TestProcessAttributeValueExponent(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "50000"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_VAL_EXP"},
|
||||
@@ -2372,7 +2372,7 @@ func TestProcessAttributeUnixTimeStamp(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error: %+v", err)
|
||||
}
|
||||
clnEv := ev.Clone()
|
||||
clnEv := ev.CGREvent.Clone()
|
||||
clnEv.Event["Field2"] = "1388415601"
|
||||
eRply := &AttrSProcessEventReply{
|
||||
MatchedProfiles: []string{"ATTR_UNIX_TIMESTAMP"},
|
||||
|
||||
@@ -170,7 +170,7 @@ func (cdrS *CDRServer) rateCDR(cdr *CDRWithArgDispatcher) ([]*CDR, error) {
|
||||
}
|
||||
if len(smCosts) != 0 { // Cost retrieved from SMCost table
|
||||
for _, smCost := range smCosts {
|
||||
cdrClone := cdr.Clone()
|
||||
cdrClone := cdr.CDR.Clone()
|
||||
cdrClone.OriginID = smCost.OriginID
|
||||
if cdr.Usage == 0 {
|
||||
cdrClone.Usage = smCost.Usage
|
||||
|
||||
@@ -2584,6 +2584,7 @@ func TestTPRoutesAsTPRouteProfile(t *testing.T) {
|
||||
sort.Slice(rcvRev[0].Routes, func(i, j int) bool {
|
||||
return strings.Compare(rcvRev[0].Routes[i].ID, rcvRev[0].Routes[j].ID) < 0
|
||||
})
|
||||
sort.Strings(rcvRev[0].SortingParameters)
|
||||
if !reflect.DeepEqual(rcvRev, expPrfRev) {
|
||||
t.Errorf("Expecting: %+v,\nReceived: %+v", utils.ToJSON(expPrfRev), utils.ToJSON(rcvRev))
|
||||
}
|
||||
|
||||
@@ -1344,11 +1344,27 @@ type SessionFilter struct {
|
||||
*ArgDispatcher
|
||||
}
|
||||
|
||||
// ArgDispatcher the basic information for dispatcher
|
||||
type ArgDispatcher struct {
|
||||
APIKey *string
|
||||
RouteID *string
|
||||
}
|
||||
|
||||
// Clone returns a copy of the ArgDispatcher
|
||||
func (arg *ArgDispatcher) Clone() (clned *ArgDispatcher) {
|
||||
if arg == nil {
|
||||
return
|
||||
}
|
||||
clned = new(ArgDispatcher)
|
||||
if arg.APIKey != nil {
|
||||
clned.APIKey = StringPointer(*arg.APIKey)
|
||||
}
|
||||
if arg.RouteID != nil {
|
||||
clned.RouteID = StringPointer(*arg.RouteID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type RatingPlanCostArg struct {
|
||||
RatingPlanIDs []string
|
||||
Destination string
|
||||
|
||||
@@ -170,8 +170,7 @@ func (ev *CGREventWithArgDispatcher) Clone() (clned *CGREventWithArgDispatcher)
|
||||
clned.CGREvent = ev.CGREvent.Clone()
|
||||
}
|
||||
if ev.ArgDispatcher != nil {
|
||||
clned.ArgDispatcher = new(ArgDispatcher)
|
||||
*clned.ArgDispatcher = *ev.ArgDispatcher
|
||||
clned.ArgDispatcher = ev.ArgDispatcher.Clone()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -268,6 +267,7 @@ func ExtractArgsFromOpts(ev map[string]interface{}, dispatcherFlag, consumeRoute
|
||||
return
|
||||
}
|
||||
|
||||
// Clone return a copy of the CGREventWithOpts
|
||||
func (ev *CGREventWithOpts) Clone() (clned *CGREventWithOpts) {
|
||||
if ev == nil {
|
||||
return
|
||||
@@ -276,10 +276,7 @@ func (ev *CGREventWithOpts) Clone() (clned *CGREventWithOpts) {
|
||||
if ev.CGREvent != nil {
|
||||
clned.CGREvent = ev.CGREvent.Clone()
|
||||
}
|
||||
if ev.ArgDispatcher != nil {
|
||||
clned.ArgDispatcher = new(ArgDispatcher)
|
||||
*clned.ArgDispatcher = *ev.ArgDispatcher
|
||||
}
|
||||
clned.ArgDispatcher = ev.ArgDispatcher.Clone()
|
||||
if ev.Opts != nil {
|
||||
clned.Opts = make(map[string]interface{})
|
||||
for opt, val := range ev.Opts {
|
||||
|
||||
@@ -517,5 +517,96 @@ func TestCGREventWithArgDispatcherClone(t *testing.T) {
|
||||
if reflect.DeepEqual(cgrEventWithArgDispatcher.ArgDispatcher, rcv.ArgDispatcher) {
|
||||
t.Errorf("Expected to be different")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCGREventWithOptsClone(t *testing.T) {
|
||||
//empty check
|
||||
cgrEventWithOpts := new(CGREventWithOpts)
|
||||
rcv := cgrEventWithOpts.Clone()
|
||||
if !reflect.DeepEqual(cgrEventWithOpts, rcv) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", cgrEventWithOpts, rcv)
|
||||
}
|
||||
//nil check
|
||||
cgrEventWithOpts = nil
|
||||
rcv = cgrEventWithOpts.Clone()
|
||||
if !reflect.DeepEqual(cgrEventWithOpts, rcv) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", cgrEventWithOpts, rcv)
|
||||
}
|
||||
//normal check
|
||||
now := time.Now()
|
||||
cgrEventWithOpts = &CGREventWithOpts{
|
||||
CGREvent: &CGREvent{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "IDtest",
|
||||
Time: &now,
|
||||
Event: map[string]interface{}{
|
||||
"test1": 1,
|
||||
"test2": 2,
|
||||
"test3": 3,
|
||||
},
|
||||
},
|
||||
ArgDispatcher: &ArgDispatcher{
|
||||
APIKey: StringPointer("api1"),
|
||||
RouteID: StringPointer("route1"),
|
||||
},
|
||||
Opts: map[string]interface{}{
|
||||
"Context": MetaSessionS,
|
||||
},
|
||||
}
|
||||
rcv = cgrEventWithOpts.Clone()
|
||||
if !reflect.DeepEqual(cgrEventWithOpts, rcv) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", cgrEventWithOpts, rcv)
|
||||
}
|
||||
//check vars
|
||||
rcv.ArgDispatcher = &ArgDispatcher{
|
||||
APIKey: StringPointer("apikey"),
|
||||
RouteID: StringPointer("routeid"),
|
||||
}
|
||||
if reflect.DeepEqual(cgrEventWithOpts.ArgDispatcher, rcv.ArgDispatcher) {
|
||||
t.Errorf("Expected to be different")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCGREventFieldAsInt64(t *testing.T) {
|
||||
se := &CGREvent{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "supplierEvent1",
|
||||
Event: map[string]interface{}{
|
||||
AnswerTime: time.Now(),
|
||||
"supplierprofile1": "Supplier",
|
||||
"UsageInterval": "54",
|
||||
"PddInterval": "1s",
|
||||
"Weight": 20,
|
||||
},
|
||||
}
|
||||
answ, err := se.FieldAsInt64("UsageInterval")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if answ != int64(54) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", se.Event["UsageInterval"], answ)
|
||||
}
|
||||
answ, err = se.FieldAsInt64("Weight")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if answ != int64(20) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", se.Event["Weight"], answ)
|
||||
}
|
||||
answ, err = se.FieldAsInt64("PddInterval")
|
||||
if err == nil || err.Error() != `strconv.ParseInt: parsing "1s": invalid syntax` {
|
||||
t.Errorf("Expected %s, received %s", `strconv.ParseInt: parsing "1s": invalid syntax`, err)
|
||||
}
|
||||
if answ != 0 {
|
||||
t.Errorf("Expecting: %+v, received: %+v", 0, answ)
|
||||
}
|
||||
|
||||
if _, err := se.FieldAsInt64(AnswerTime); err == nil || !strings.HasPrefix(err.Error(), "cannot convert field") {
|
||||
t.Errorf("Unexpected error : %+v", err)
|
||||
}
|
||||
if _, err := se.FieldAsInt64(Account); err == nil || err.Error() != ErrNotFound.Error() {
|
||||
t.Errorf("Expected %s, received %s", ErrNotFound, err)
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -952,6 +953,10 @@ func TestClone(t *testing.T) {
|
||||
} else if !reflect.DeepEqual(ifaceC, clndIface) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", ifaceC, clndIface)
|
||||
}
|
||||
|
||||
if err := Clone(math.NaN, nil); err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFib(t *testing.T) {
|
||||
|
||||
113
utils/dynamicdataprovider.go
Normal file
113
utils/dynamicdataprovider.go
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewDynamicDataProvider constructs a dynamic data provider
|
||||
func NewDynamicDataProvider(dp DataProvider) *DynamicDataProvider {
|
||||
return &DynamicDataProvider{
|
||||
DataProvider: dp,
|
||||
cache: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// DynamicDataProvider is a data source from multiple formats
|
||||
type DynamicDataProvider struct {
|
||||
DataProvider
|
||||
cache map[string]interface{}
|
||||
}
|
||||
|
||||
// FieldAsInterface to overwrite the FieldAsInterface function from the given DataProvider
|
||||
func (ddp *DynamicDataProvider) FieldAsInterface(fldPath []string) (out interface{}, err error) {
|
||||
path := strings.Join(fldPath, NestingSep) // join the path so we can check it in cache and parse it more easy
|
||||
if val, has := ddp.cache[path]; has { // check if we have the path in cache
|
||||
return val, nil
|
||||
}
|
||||
var newPath string
|
||||
if newPath, err = ddp.proccesFieldPath(path); err != nil { // proccess the path
|
||||
return
|
||||
}
|
||||
if newPath == EmptyString { // no new path means no dynamic path so just take the value from the data provider
|
||||
return ddp.DataProvider.FieldAsInterface(fldPath)
|
||||
}
|
||||
// split the new path and get that field
|
||||
if out, err = ddp.DataProvider.FieldAsInterface(strings.Split(newPath, NestingSep)); err != nil {
|
||||
return
|
||||
}
|
||||
// if no error save in cache the path
|
||||
ddp.cache[path] = out
|
||||
return
|
||||
}
|
||||
|
||||
func (ddp *DynamicDataProvider) proccesFieldPath(fldPath string) (newPath string, err error) {
|
||||
idx := strings.Index(fldPath, IdxStart)
|
||||
if idx == -1 {
|
||||
return // no proccessing requred
|
||||
}
|
||||
newPath = fldPath[:idx+1] // add the first path of the path with the "[" included
|
||||
for idx != -1 { // stop when we do not find any "["
|
||||
fldPath = fldPath[idx+1:] // move the path to the begining of the index
|
||||
nextBeginIdx := strings.Index(fldPath, IdxStart) // get the next "[" if any
|
||||
nextEndIdx := strings.Index(fldPath, IdxEnd) // get the next "]" if any
|
||||
if nextEndIdx == -1 { // no end index found so return error
|
||||
err = ErrWrongPath
|
||||
newPath = EmptyString
|
||||
return
|
||||
}
|
||||
|
||||
// parse the rest of the field path until we match the [ ]
|
||||
bIdx, eIdx := nextBeginIdx, nextEndIdx
|
||||
for nextBeginIdx != -1 && nextBeginIdx < nextEndIdx { // do this until no new [ is found or the next begining [ is after the end ]
|
||||
nextBeginIdx = strings.Index(fldPath[bIdx+1:], IdxStart) // get the next "[" if any
|
||||
nextEndIdx = strings.Index(fldPath[eIdx+1:], IdxEnd) // get the next "]" if any
|
||||
if nextEndIdx == -1 { // no end index found so return error
|
||||
err = ErrWrongPath
|
||||
newPath = EmptyString
|
||||
return
|
||||
}
|
||||
if nextBeginIdx == -1 { // if no index found do not increment but replace it
|
||||
bIdx = -1
|
||||
} else {
|
||||
bIdx += nextBeginIdx + 1
|
||||
}
|
||||
// increment the indexes
|
||||
eIdx += nextEndIdx + 1
|
||||
}
|
||||
var val string
|
||||
for _, path := range strings.Split(fldPath[:eIdx], PipeSep) { // proccess the found path
|
||||
var iface interface{}
|
||||
if iface, err = DPDynamicInterface(path, ddp); err != nil {
|
||||
newPath = EmptyString
|
||||
return
|
||||
}
|
||||
val += IfaceAsString(iface) // compose the value
|
||||
}
|
||||
if bIdx == -1 { // if is the last ocurence add the rest of the path and exit
|
||||
newPath += val + fldPath[eIdx:]
|
||||
} else {
|
||||
// else just add until the next [
|
||||
newPath += val + fldPath[eIdx:bIdx+1]
|
||||
}
|
||||
idx = bIdx
|
||||
}
|
||||
return
|
||||
}
|
||||
143
utils/dynamicdataprovider_test.go
Normal file
143
utils/dynamicdataprovider_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDynamicDataProviderProccesFieldPath(t *testing.T) {
|
||||
dp := MapStorage{
|
||||
MetaCgrep: MapStorage{
|
||||
"Stir": MapStorage{
|
||||
"CHRG_ROUTE1_END": "Identity1",
|
||||
"CHRG_ROUTE2_END": "Identity2",
|
||||
"CHRG_ROUTE3_END": "Identity3",
|
||||
"CHRG_ROUTE4_END": "Identity4",
|
||||
},
|
||||
"Routes": MapStorage{
|
||||
"SortedRoutes": []MapStorage{
|
||||
{"ID": "ROUTE1"},
|
||||
{"ID": "ROUTE2"},
|
||||
{"ID": "ROUTE3"},
|
||||
{"ID": "ROUTE4"},
|
||||
},
|
||||
},
|
||||
"BestRoute": 0,
|
||||
},
|
||||
}
|
||||
ddp := NewDynamicDataProvider(dp)
|
||||
newpath, err := ddp.proccesFieldPath("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID|_END].Something[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedPath := "~*cgrep.Stir[CHRG_ROUTE2_END].Something[CHRG_ROUTE1_END]"
|
||||
if newpath != expectedPath {
|
||||
t.Errorf("Expected: %q,received %q", expectedPath, newpath)
|
||||
}
|
||||
_, err = ddp.proccesFieldPath("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID|_END].Something[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END")
|
||||
if err != ErrWrongPath {
|
||||
t.Errorf("Expected error %s received %v", ErrWrongPath, err)
|
||||
}
|
||||
|
||||
_, err = ddp.proccesFieldPath("~*cgrep.Stir[CHRG_")
|
||||
if err != ErrWrongPath {
|
||||
t.Errorf("Expected error %s received %v", ErrWrongPath, err)
|
||||
}
|
||||
|
||||
_, err = ddp.proccesFieldPath("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID2|_END]")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Expected error %s received %v", ErrNotFound, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDynamicDataProviderFieldAsInterface(t *testing.T) {
|
||||
dp := MapStorage{
|
||||
MetaCgrep: MapStorage{
|
||||
"Stir": MapStorage{
|
||||
"CHRG_ROUTE1_END": "Identity1",
|
||||
"CHRG_ROUTE2_END": "Identity2",
|
||||
"CHRG_ROUTE3_END": "Identity3",
|
||||
"CHRG_ROUTE4_END": "Identity4",
|
||||
},
|
||||
"Routes": MapStorage{
|
||||
"SortedRoutes": []MapStorage{
|
||||
{"ID": "ROUTE1"},
|
||||
{"ID": "ROUTE2"},
|
||||
{"ID": "ROUTE3"},
|
||||
{"ID": "ROUTE4"},
|
||||
},
|
||||
},
|
||||
"BestRoute": 0,
|
||||
},
|
||||
}
|
||||
ddp := NewDynamicDataProvider(dp)
|
||||
path := "*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]"
|
||||
out, err := ddp.FieldAsInterface(strings.Split(path, NestingSep))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Identity1"
|
||||
if out != expected {
|
||||
t.Errorf("Expected: %q,received %q", expected, out)
|
||||
}
|
||||
path = "*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END"
|
||||
_, err = ddp.FieldAsInterface(strings.Split(path, NestingSep))
|
||||
if err != ErrWrongPath {
|
||||
t.Errorf("Expected error %s received %v", ErrWrongPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicDataProviderFieldAsInterface2(t *testing.T) {
|
||||
dp := MapStorage{
|
||||
MetaCgrep: MapStorage{
|
||||
"Stir": map[string]interface{}{
|
||||
"CHRG_ROUTE1_END": "Identity1",
|
||||
"CHRG_ROUTE2_END": "Identity2",
|
||||
"CHRG_ROUTE3_END": "Identity3",
|
||||
"CHRG_ROUTE4_END": "Identity4",
|
||||
},
|
||||
"Routes": map[string]interface{}{
|
||||
"SortedRoutes": []map[string]interface{}{
|
||||
{"ID": "ROUTE1"},
|
||||
{"ID": "ROUTE2"},
|
||||
{"ID": "ROUTE3"},
|
||||
{"ID": "ROUTE4"},
|
||||
},
|
||||
},
|
||||
"BestRoute": 0,
|
||||
},
|
||||
}
|
||||
ddp := NewDynamicDataProvider(dp)
|
||||
path := "*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]"
|
||||
out, err := ddp.FieldAsInterface(strings.Split(path, NestingSep))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Identity1"
|
||||
if out != expected {
|
||||
t.Errorf("Expected: %q,received %q", expected, out)
|
||||
}
|
||||
path = "*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END"
|
||||
_, err = ddp.FieldAsInterface(strings.Split(path, NestingSep))
|
||||
if err != ErrWrongPath {
|
||||
t.Errorf("Expected error %s received %v", ErrWrongPath, err)
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -47,34 +48,48 @@ func (ms MapStorage) FieldAsInterface(fldPath []string) (val interface{}, err er
|
||||
if len(fldPath) == 0 {
|
||||
err = errors.New("empty field path")
|
||||
return
|
||||
|
||||
}
|
||||
opath, indx := GetPathIndex(fldPath[0])
|
||||
opath, sindx := GetPathIndexString(fldPath[0])
|
||||
var has bool
|
||||
if val, has = ms[opath]; !has {
|
||||
err = ErrNotFound
|
||||
return
|
||||
}
|
||||
if len(fldPath) == 1 {
|
||||
if indx == nil {
|
||||
if sindx == nil {
|
||||
return
|
||||
|
||||
}
|
||||
switch rv := val.(type) {
|
||||
case []string:
|
||||
if len(rv) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rv) <= indx {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
val = rv[*indx]
|
||||
val = rv[indx]
|
||||
return
|
||||
case []interface{}:
|
||||
if len(rv) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(rv) <= indx {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
val = rv[*indx]
|
||||
val = rv[indx]
|
||||
return
|
||||
case DataProvider:
|
||||
return rv.FieldAsInterface(append([]string{*sindx}, fldPath[1:]...))
|
||||
case map[string]interface{}:
|
||||
return MapStorage(rv).FieldAsInterface(append([]string{*sindx}, fldPath[1:]...))
|
||||
default:
|
||||
}
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
// only if all above fails use reflect:
|
||||
vr := reflect.ValueOf(val)
|
||||
if vr.Kind() == reflect.Ptr {
|
||||
@@ -84,13 +99,12 @@ func (ms MapStorage) FieldAsInterface(fldPath []string) (val interface{}, err er
|
||||
return nil, ErrNotFound
|
||||
|
||||
}
|
||||
if *indx >= vr.Len() {
|
||||
if indx >= vr.Len() {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return vr.Index(*indx).Interface(), nil
|
||||
|
||||
return vr.Index(indx).Interface(), nil
|
||||
}
|
||||
if indx == nil {
|
||||
if sindx == nil {
|
||||
switch dp := ms[fldPath[0]].(type) {
|
||||
case DataProvider:
|
||||
return dp.FieldAsInterface(fldPath[1:])
|
||||
@@ -98,32 +112,51 @@ func (ms MapStorage) FieldAsInterface(fldPath []string) (val interface{}, err er
|
||||
return MapStorage(dp).FieldAsInterface(fldPath[1:])
|
||||
default:
|
||||
err = ErrWrongPath
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
switch dp := ms[opath].(type) {
|
||||
case DataProvider:
|
||||
return dp.FieldAsInterface(append([]string{*sindx}, fldPath[1:]...))
|
||||
case map[string]interface{}:
|
||||
return MapStorage(dp).FieldAsInterface(append([]string{*sindx}, fldPath[1:]...))
|
||||
case []DataProvider:
|
||||
if len(dp) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(dp) <= indx {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return dp[*indx].FieldAsInterface(fldPath[1:])
|
||||
return dp[indx].FieldAsInterface(fldPath[1:])
|
||||
case []MapStorage:
|
||||
if len(dp) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(dp) <= indx {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return dp[*indx].FieldAsInterface(fldPath[1:])
|
||||
return dp[indx].FieldAsInterface(fldPath[1:])
|
||||
case []map[string]interface{}:
|
||||
if len(dp) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(dp) <= indx {
|
||||
return nil, ErrNotFound
|
||||
|
||||
}
|
||||
return MapStorage(dp[*indx]).FieldAsInterface(fldPath[1:])
|
||||
return MapStorage(dp[indx]).FieldAsInterface(fldPath[1:])
|
||||
case []interface{}:
|
||||
if len(dp) <= *indx {
|
||||
var indx int
|
||||
if indx, err = strconv.Atoi(*sindx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(dp) <= indx {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
switch ds := dp[*indx].(type) {
|
||||
switch ds := dp[indx].(type) {
|
||||
case DataProvider:
|
||||
return ds.FieldAsInterface(fldPath[1:])
|
||||
case map[string]interface{}:
|
||||
@@ -131,7 +164,6 @@ func (ms MapStorage) FieldAsInterface(fldPath []string) (val interface{}, err er
|
||||
default:
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
err = ErrNotFound // xml compatible
|
||||
val = nil
|
||||
|
||||
@@ -18,6 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -56,60 +58,6 @@ func TestNavMapGetFieldAsString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type myEv map[string]interface{}
|
||||
|
||||
func (ev myEv) AsMapStorage() (MapStorage, error) {
|
||||
return MapStorage(ev), nil
|
||||
}
|
||||
|
||||
func TestNavMapAsMapStorage(t *testing.T) {
|
||||
myData := myEv{
|
||||
"FirstLevel": map[string]interface{}{
|
||||
"SecondLevel": map[string]interface{}{
|
||||
"ThirdLevel": map[string]interface{}{
|
||||
"Fld1": 123.123,
|
||||
},
|
||||
},
|
||||
},
|
||||
"FistLever2": map[string]interface{}{
|
||||
"SecondLevel2": map[string]interface{}{
|
||||
"Field2": 123,
|
||||
},
|
||||
"Field3": "Value3",
|
||||
},
|
||||
"Field4": &testStruct{
|
||||
Item1: "Ten",
|
||||
Item2: 10,
|
||||
},
|
||||
}
|
||||
|
||||
eNavMap := MapStorage{
|
||||
"FirstLevel": map[string]interface{}{
|
||||
"SecondLevel": map[string]interface{}{
|
||||
"ThirdLevel": map[string]interface{}{
|
||||
"Fld1": 123.123,
|
||||
},
|
||||
},
|
||||
},
|
||||
"FistLever2": map[string]interface{}{
|
||||
"SecondLevel2": map[string]interface{}{
|
||||
"Field2": 123,
|
||||
},
|
||||
"Field3": "Value3",
|
||||
},
|
||||
"Field4": &testStruct{
|
||||
Item1: "Ten",
|
||||
Item2: 10,
|
||||
},
|
||||
}
|
||||
|
||||
if rcv, err := myData.AsMapStorage(); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eNavMap, rcv) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", eNavMap, rcv)
|
||||
}
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
Item1 string
|
||||
Item2 int
|
||||
@@ -346,3 +294,133 @@ func TestNavMapGetKeys(t *testing.T) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", ToJSON(expKeys), ToJSON(keys))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavMapFieldAsInterface2(t *testing.T) {
|
||||
nM := MapStorage{
|
||||
"Slice": &[]struct{}{{}},
|
||||
"SliceString": []string{"1", "2"},
|
||||
"SliceInterface": []interface{}{1, "2"},
|
||||
}
|
||||
|
||||
path := []string{"Slice[1]"}
|
||||
expErr := 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{"Slice[nan]"}
|
||||
expErr = fmt.Errorf(`strconv.Atoi: parsing "nan": invalid syntax`)
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = []string{"Slice[0]"}
|
||||
eVal = struct{}{}
|
||||
if rplyVal, err := nM.FieldAsInterface(path); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eVal, rplyVal) {
|
||||
t.Errorf("Expected: %s , received: %s", ToJSON(eVal), ToJSON(rplyVal))
|
||||
}
|
||||
|
||||
path = []string{"AnotherFirstLevel[1]"}
|
||||
expErr = ErrNotFound
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = []string{"SliceString[nan]"}
|
||||
expErr = fmt.Errorf(`strconv.Atoi: parsing "nan": invalid syntax`)
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = []string{"SliceInterface[nan]"}
|
||||
expErr = fmt.Errorf(`strconv.Atoi: parsing "nan": invalid syntax`)
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = []string{"SliceString[4]"}
|
||||
expErr = ErrNotFound
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = []string{"SliceInterface[4]"}
|
||||
expErr = ErrNotFound
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
|
||||
path = nil
|
||||
expErr = errors.New("empty field path")
|
||||
if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() {
|
||||
t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapStorageRemote(t *testing.T) {
|
||||
nm := MapStorage{}
|
||||
eOut := LocalAddr()
|
||||
if rcv := nm.RemoteHost(); !reflect.DeepEqual(eOut, rcv) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", eOut, rcv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavMapGetField2(t *testing.T) {
|
||||
nM := MapStorage{
|
||||
"FirstLevel": MapStorage{
|
||||
"SecondLevel": MapStorage{
|
||||
"ThirdLevel": MapStorage{
|
||||
"Fld1": []interface{}{"Val1", "Val2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"FirstLevel2": MapStorage{
|
||||
"SecondLevel2": []MapStorage{
|
||||
MapStorage{
|
||||
"ThirdLevel2": MapStorage{
|
||||
"Fld1": "Val1",
|
||||
},
|
||||
},
|
||||
MapStorage{
|
||||
"Count": 10,
|
||||
"ThirdLevel2": MapStorage{
|
||||
"Fld2": []string{"Val1", "Val2", "Val3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"AnotherFirstLevel": "ValAnotherFirstLevel",
|
||||
}
|
||||
pth := []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1[0]"}
|
||||
eFld := "Val1"
|
||||
if fld, err := nM.FieldAsInterface(pth); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eFld, fld) {
|
||||
t.Errorf("expecting: %s, received: %s", ToIJSON(eFld), ToIJSON(fld))
|
||||
}
|
||||
eFld2 := MapStorage{"Fld1": "Val1"}
|
||||
pth = []string{"FirstLevel2", "SecondLevel2[0]", "ThirdLevel2"}
|
||||
if fld, err := nM.FieldAsInterface(pth); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eFld2, fld) {
|
||||
t.Errorf("expecting: %s, received: %s", ToIJSON(eFld2), ToIJSON(fld))
|
||||
}
|
||||
eFld3 := "ValAnotherFirstLevel"
|
||||
pth = []string{"AnotherFirstLevel"}
|
||||
if fld, err := nM.FieldAsInterface(pth); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eFld3, fld) {
|
||||
t.Errorf("expecting: %s, received: %s", ToIJSON(eFld3), ToIJSON(fld))
|
||||
}
|
||||
pth = []string{"AnotherFirstLevel2"}
|
||||
if _, err := nM.FieldAsInterface(pth); err == nil || err != ErrNotFound {
|
||||
t.Error(err)
|
||||
}
|
||||
pth = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel", "Fld1[0]"}
|
||||
if _, err := nM.FieldAsInterface(pth); err == nil || err != ErrNotFound {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,3 +147,13 @@ func GetPathWithoutIndex(spath string) (opath string) {
|
||||
opath = spath[:idxStart]
|
||||
return
|
||||
}
|
||||
|
||||
func GetPathIndexString(spath string) (opath string, idx *string) {
|
||||
idxStart := strings.Index(spath, IdxStart)
|
||||
if idxStart == -1 || !strings.HasSuffix(spath, IdxEnd) {
|
||||
return spath, nil
|
||||
}
|
||||
idxVal := spath[idxStart+1 : len(spath)-1]
|
||||
opath = spath[:idxStart]
|
||||
return opath, &idxVal
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user