diff --git a/engine/attributes_test.go b/engine/attributes_test.go
index 78e965927..5ce90a477 100644
--- a/engine/attributes_test.go
+++ b/engine/attributes_test.go
@@ -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"},
diff --git a/engine/cdrs.go b/engine/cdrs.go
index cf2a9a81d..4579b23a0 100644
--- a/engine/cdrs.go
+++ b/engine/cdrs.go
@@ -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
diff --git a/engine/model_helpers_test.go b/engine/model_helpers_test.go
index 425569d32..c16b371d9 100644
--- a/engine/model_helpers_test.go
+++ b/engine/model_helpers_test.go
@@ -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))
}
diff --git a/utils/apitpdata.go b/utils/apitpdata.go
index 0f71883e4..34decd585 100755
--- a/utils/apitpdata.go
+++ b/utils/apitpdata.go
@@ -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
diff --git a/utils/cgrevent.go b/utils/cgrevent.go
index f5e441a4e..dbd797828 100644
--- a/utils/cgrevent.go
+++ b/utils/cgrevent.go
@@ -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 {
diff --git a/utils/cgrevent_test.go b/utils/cgrevent_test.go
index e9d34a3eb..71c65840b 100644
--- a/utils/cgrevent_test.go
+++ b/utils/cgrevent_test.go
@@ -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)
+ }
+ // }
+}
diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go
index 5d39ec7af..cf39883c8 100644
--- a/utils/coreutils_test.go
+++ b/utils/coreutils_test.go
@@ -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) {
diff --git a/utils/dynamicdataprovider.go b/utils/dynamicdataprovider.go
new file mode 100644
index 000000000..4d199cb88
--- /dev/null
+++ b/utils/dynamicdataprovider.go
@@ -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
+*/
+
+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
+}
diff --git a/utils/dynamicdataprovider_test.go b/utils/dynamicdataprovider_test.go
new file mode 100644
index 000000000..30c668b0c
--- /dev/null
+++ b/utils/dynamicdataprovider_test.go
@@ -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
+*/
+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)
+ }
+}
diff --git a/utils/mapstorage.go b/utils/mapstorage.go
index 4819cca3d..41cc45abc 100644
--- a/utils/mapstorage.go
+++ b/utils/mapstorage.go
@@ -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
diff --git a/utils/mapstorage_test.go b/utils/mapstorage_test.go
index a462f354f..c007a5d03 100644
--- a/utils/mapstorage_test.go
+++ b/utils/mapstorage_test.go
@@ -18,6 +18,8 @@ along with this program. If not, see
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)
+ }
+}
diff --git a/utils/pathitem.go b/utils/pathitem.go
index 66ca0d7b7..3d16f173f 100644
--- a/utils/pathitem.go
+++ b/utils/pathitem.go
@@ -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
+}