diff --git a/agents/kamevent.go b/agents/kamevent.go
index cf4a018c6..59fba8762 100644
--- a/agents/kamevent.go
+++ b/agents/kamevent.go
@@ -439,7 +439,6 @@ func NewKamDlgReply(kamEvData []byte) (rpl KamDlgReply, err error) {
return
}
-func (self *KamDlgReply) String() string {
- mrsh, _ := json.Marshal(self)
- return string(mrsh)
+func (kdr *KamDlgReply) String() string {
+ return utils.ToJSON(kdr)
}
diff --git a/config/configsanity.go b/config/configsanity.go
index 2077370ae..fe2f231c4 100644
--- a/config/configsanity.go
+++ b/config/configsanity.go
@@ -198,7 +198,7 @@ func (cfg *CGRConfig) checkConfigSanity() error {
if cfg.cacheCfg[utils.CacheClosedSessions].Limit == 0 {
return fmt.Errorf("<%s> %s needs to be != 0, received: %d", utils.CacheS, utils.CacheClosedSessions, cfg.cacheCfg[utils.CacheClosedSessions].Limit)
}
- for alfld := range cfg.sessionSCfg.AlterableFields.Data() {
+ for alfld := range cfg.sessionSCfg.AlterableFields {
if utils.ProtectedSFlds.Has(alfld) {
return fmt.Errorf("<%s> the following protected field can't be altered by session: <%s>", utils.SessionS, alfld)
}
diff --git a/config/smconfig.go b/config/smconfig.go
index d790726db..1c2a54f72 100644
--- a/config/smconfig.go
+++ b/config/smconfig.go
@@ -97,7 +97,7 @@ type SessionSCfg struct {
ClientProtocol float64
ChannelSyncInterval time.Duration
TerminateAttempts int
- AlterableFields *utils.StringSet
+ AlterableFields utils.StringSet
}
func (scfg *SessionSCfg) loadFromJsonCfg(jsnCfg *SessionSJsonCfg) (err error) {
diff --git a/engine/datamanager.go b/engine/datamanager.go
index 5ff7aaaa3..e36fc05df 100644
--- a/engine/datamanager.go
+++ b/engine/datamanager.go
@@ -1804,7 +1804,7 @@ func (dm *DataManager) GetAttributeProfile(tenant, id string, cacheRead, cacheWr
}
}
isInline := false
- for typeAttr := range utils.AttrInlineTypes.Data() {
+ for typeAttr := range utils.AttrInlineTypes {
if strings.HasPrefix(id, typeAttr) {
isInline = true
break
diff --git a/engine/filters.go b/engine/filters.go
index 7ebfbcf3b..fb93e5064 100644
--- a/engine/filters.go
+++ b/engine/filters.go
@@ -129,16 +129,16 @@ func (fltr *Filter) Compile() (err error) {
return
}
-var supportedFiltersType *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix,
+var supportedFiltersType utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix,
utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual,
utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual,
utils.MetaNotEqual})
-var needsFieldName *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
+var needsFieldName utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
utils.MetaSuffix, utils.MetaTimings, utils.MetaDestinations, utils.MetaLessThan,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan,
utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaNotEqual})
-var needsValues *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
+var needsValues utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
utils.MetaSuffix, utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations,
utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual,
utils.MetaEqual, utils.MetaNotEqual})
diff --git a/engine/mapevent.go b/engine/mapevent.go
index 55cf388b6..5eda54077 100644
--- a/engine/mapevent.go
+++ b/engine/mapevent.go
@@ -181,7 +181,7 @@ func (me MapEvent) Clone() (mp MapEvent) {
// AsMapString returns a map[string]string out of mp, ignoring specific fields if needed
// most used when needing to export extraFields
-func (me MapEvent) AsMapString(ignoredFlds *utils.StringSet) (mp map[string]string) {
+func (me MapEvent) AsMapString(ignoredFlds utils.StringSet) (mp map[string]string) {
mp = make(map[string]string)
if ignoredFlds == nil {
ignoredFlds = utils.NewStringSet(nil)
diff --git a/engine/safevent.go b/engine/safevent.go
index eafa65ce6..7892b0586 100644
--- a/engine/safevent.go
+++ b/engine/safevent.go
@@ -240,7 +240,7 @@ func (se *SafEvent) AsMapInterface() (mp map[string]interface{}) {
// AsMapString returns a map[string]string out of mp, ignoring specific fields if needed
// most used when needing to export extraFields
-func (se *SafEvent) AsMapString(ignoredFlds *utils.StringSet) (mp map[string]string) {
+func (se *SafEvent) AsMapString(ignoredFlds utils.StringSet) (mp map[string]string) {
se.RLock()
mp = se.Me.AsMapString(ignoredFlds)
se.RUnlock()
diff --git a/engine/storage_internal_stordb.go b/engine/storage_internal_stordb.go
index 6da1b60de..82b08884c 100644
--- a/engine/storage_internal_stordb.go
+++ b/engine/storage_internal_stordb.go
@@ -1081,7 +1081,7 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
}
// find indexed fields
- var cdrMpIDs *utils.StringSet
+ var cdrMpIDs utils.StringSet
// Apply string filter
for _, fltrSlc := range pairSlice {
if len(fltrSlc.ids) == 0 {
@@ -1158,7 +1158,7 @@ func (iDB *InternalDB) GetCDRs(filter *utils.CDRsFilter, remove bool) (cdrs []*C
}
paginatorOffsetCounter := 0
- for key := range cdrMpIDs.Data() {
+ for key := range cdrMpIDs {
x, ok := iDB.db.Get(utils.CDRsTBL, key)
if !ok || x == nil {
return nil, 0, utils.ErrNotFound
diff --git a/sessions/session.go b/sessions/session.go
index a5c7c5f03..c7a5bc1d5 100644
--- a/sessions/session.go
+++ b/sessions/session.go
@@ -307,7 +307,7 @@ func (sr *SRun) debitReserve(dur time.Duration, lastUsage *time.Duration) (rDur
}
// updateSRuns updates the SRuns event with the alterable fields (is not thread safe)
-func (s *Session) updateSRuns(updEv engine.MapEvent, alterableFields *utils.StringSet) {
+func (s *Session) updateSRuns(updEv engine.MapEvent, alterableFields utils.StringSet) {
if alterableFields.Size() == 0 {
return
}
@@ -322,7 +322,7 @@ func (s *Session) updateSRuns(updEv engine.MapEvent, alterableFields *utils.Stri
}
// UpdateSRuns updates the SRuns event with the alterable fields (is thread safe)
-func (s *Session) UpdateSRuns(updEv engine.MapEvent, alterableFields *utils.StringSet) {
+func (s *Session) UpdateSRuns(updEv engine.MapEvent, alterableFields utils.StringSet) {
if alterableFields.Size() == 0 { // do not lock if we can't update any field
return
}
diff --git a/utils/coreutils.go b/utils/coreutils.go
index 037adbd13..262d4be7f 100644
--- a/utils/coreutils.go
+++ b/utils/coreutils.go
@@ -895,23 +895,3 @@ func CastRPCErr(err error) error {
func RandomInteger(min, max int) int {
return math_rand.Intn(max-min) + min
}
-
-// GetPathIndex returns the path and index if index present
-// path[index]=>path,index
-// path=>path,nil
-func GetPathIndex(spath string) (opath string, idx *int) {
- idxStart := strings.Index(spath, IdxStart)
- if idxStart == -1 || !strings.HasSuffix(spath, IdxEnd) {
- return spath, nil
- }
- slctr := spath[idxStart+1 : len(spath)-1]
- opath = spath[:idxStart]
- // if strings.HasPrefix(slctr, DynamicDataPrefix) {
- // return
- // }
- idxVal, err := strconv.Atoi(slctr)
- if err != nil {
- return spath, nil
- }
- return opath, &idxVal
-}
diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go
index 811418133..8f09d3722 100644
--- a/utils/coreutils_test.go
+++ b/utils/coreutils_test.go
@@ -1307,7 +1307,7 @@ func TestRandomInteger(t *testing.T) {
t.Errorf("same result over 3 attempts")
}
if a >= 100 || b >= 100 || c >= 100 {
- t.Errorf("one of the numbers equals the (/are above) max limit")
+ t.Errorf("one of the numbers equals or it's above the max limit")
}
if a < 0 || b < 0 || c < 0 {
t.Errorf("one of the numbers are below min limit")
diff --git a/utils/dataprovider.go b/utils/dataprovider.go
new file mode 100644
index 000000000..e7bc6e9c2
--- /dev/null
+++ b/utils/dataprovider.go
@@ -0,0 +1,123 @@
+/*
+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 (
+ "net"
+ "strings"
+)
+
+// posible NMType
+const (
+ NMDataType NMType = iota
+ NMMapType
+ NMSliceType
+)
+
+// DataProvider is a data source from multiple formats
+type DataProvider interface {
+ String() string // printable version of data
+ FieldAsInterface(fldPath []string) (interface{}, error)
+ FieldAsString(fldPath []string) (string, error) // remove this
+ RemoteHost() net.Addr
+}
+
+// NavigableMapper is the interface supported by replies convertible to CGRReply
+type NavigableMapper interface {
+ AsNavigableMap() NavigableMap2
+}
+
+// DPDynamicInterface returns the value of the field if the path is dynamic
+func DPDynamicInterface(dnVal string, dP DataProvider) (interface{}, error) {
+ if strings.HasPrefix(dnVal, DynamicDataPrefix) {
+ dnVal = strings.TrimPrefix(dnVal, DynamicDataPrefix)
+ return dP.FieldAsInterface(strings.Split(dnVal, NestingSep))
+ }
+ return StringToInterface(dnVal), nil
+}
+
+// DPDynamicString returns the string value of the field if the path is dynamic
+func DPDynamicString(dnVal string, dP DataProvider) (string, error) {
+ if strings.HasPrefix(dnVal, DynamicDataPrefix) {
+ dnVal = strings.TrimPrefix(dnVal, DynamicDataPrefix)
+ return dP.FieldAsString(strings.Split(dnVal, NestingSep))
+ }
+ return dnVal, nil
+}
+
+// NMType the type used for navigable Map
+type NMType byte
+
+// NMInterface the basic interface
+type NMInterface interface {
+ String() string
+ Interface() interface{}
+ Field(path PathItems) (val NMInterface, err error)
+ Set(path PathItems, val NMInterface) (addedNew bool, err error)
+ Remove(path PathItems) (err error)
+ Type() NMType
+ Empty() bool
+ Len() int
+}
+
+// navMap subset of function for NM interface
+type navMap interface {
+ Field(path PathItems) (val NMInterface, err error)
+ Set(path PathItems, val NMInterface) (addedNew bool, err error)
+}
+
+// AppendNavMapVal appends value to the map
+func AppendNavMapVal(nm navMap, fldPath PathItems, val NMInterface) (err error) {
+ var prevItm NMInterface
+ var indx int
+ if prevItm, err = nm.Field(fldPath); err != nil {
+ if err != ErrNotFound {
+ return
+ }
+ } else {
+ indx = prevItm.Len()
+ }
+ fldPath[len(fldPath)-1].Index = &indx
+ _, err = nm.Set(fldPath, val)
+ return
+}
+
+// ComposeNavMapVal compose adds value to prevision item
+func ComposeNavMapVal(nm navMap, fldPath PathItems, val NMInterface) (err error) {
+ var prevItmSlice NMInterface
+ var indx int
+ if prevItmSlice, err = nm.Field(fldPath); err != nil {
+ if err != ErrNotFound {
+ return
+ }
+ } else {
+ indx = prevItmSlice.Len() - 1
+ var prevItm NMInterface
+ if prevItm, err = prevItmSlice.Field(PathItems{{Index: &indx}}); err != nil {
+ if err != ErrNotFound {
+ return
+ }
+ } else if _, err = val.Set(nil, NewNMData(IfaceAsString(prevItm.Interface())+IfaceAsString(val.Interface()))); err != nil {
+ return
+ }
+ }
+ fldPath[len(fldPath)-1].Index = &indx
+ _, err = nm.Set(fldPath, val)
+ return
+}
diff --git a/utils/dataprovider_test.go b/utils/dataprovider_test.go
new file mode 100644
index 000000000..a197d958e
--- /dev/null
+++ b/utils/dataprovider_test.go
@@ -0,0 +1,190 @@
+/*
+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 (
+ "reflect"
+ "testing"
+)
+
+func TestDPDynamicInterface(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ var expected interface{} = "Field5[1]"
+ if rply, err := DPDynamicInterface("Field5[1]", nm); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+
+ expected = 101
+ if rply, err := DPDynamicInterface("~Field5[1]", nm); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %v ,received: %v", expected, rply)
+ }
+
+}
+
+func TestDPDynamicString(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ var expected interface{} = "Field5[1]"
+ if rply, err := DPDynamicString("Field5[1]", nm); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+
+ expected = "101"
+ if rply, err := DPDynamicString("~Field5[1]", nm); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %v ,received: %v", expected, rply)
+ }
+
+}
+
+func TestAppendNavMapVal(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ expected := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101), NewNMData(18)},
+ }
+ if err := AppendNavMapVal(nm, PathItems{{Field: "Field5"}}, NewNMData(18)); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, nm) {
+ t.Errorf("Expected %v ,received: %v", expected, nm)
+ }
+ if err := AppendNavMapVal(nm, nil, NewNMData(18)); err != ErrWrongPath {
+ t.Error(err)
+ }
+}
+
+func TestComposeNavMapVal(t *testing.T) {
+ nm := NavigableMap2{
+ "Field4": &NMSlice{},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if err := ComposeNavMapVal(nm, nil, NewNMData(18)); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := ComposeNavMapVal(nm, PathItems{{Field: "Field4"}}, NewNMData(18)); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected := NavigableMap2{
+ "Field4": &NMSlice{},
+ "Field5": &NMSlice{NewNMData(10), NewNMData("10118")},
+ }
+ if err := ComposeNavMapVal(nm, PathItems{{Field: "Field5"}}, NewNMData(18)); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, nm) {
+ t.Errorf("Expected %v ,received: %v", expected, nm)
+ }
+
+ expected = NavigableMap2{
+ "Field4": &NMSlice{},
+ "Field5": &NMSlice{NewNMData(10), NewNMData("10118")},
+ "Field6": &NMSlice{NewNMData(10)},
+ }
+ if err := ComposeNavMapVal(nm, PathItems{{Field: "Field6"}}, NewNMData(10)); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expected, nm) {
+ t.Errorf("Expected %v ,received: %v", expected, nm)
+ }
+
+ nm = NavigableMap2{
+ "Field4": NewNMData(1),
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if err := ComposeNavMapVal(nm, PathItems{{Field: "Field4"}}, NewNMData(10)); err != ErrNotImplemented {
+ t.Error(err)
+ }
+
+ if err := ComposeNavMapVal(nm, PathItems{{Field: "Field5"}}, &mockNMInterface{data: 10}); err != ErrNotImplemented {
+ t.Error(err)
+ }
+}
+
+// mock NMInterface structure
+
+type mockNMInterface struct{ data interface{} }
+
+func (nmi *mockNMInterface) String() string {
+ return IfaceAsString(nmi.data)
+}
+
+// Interface returns the wraped interface
+func (nmi *mockNMInterface) Interface() interface{} {
+ return nmi.data
+}
+
+// Field not implemented only used in order to implement the NM interface
+func (nmi *mockNMInterface) Field(path PathItems) (val NMInterface, err error) {
+ return nil, ErrNotImplemented
+}
+
+// Set not implemented
+func (nmi *mockNMInterface) Set(path PathItems, val NMInterface) (a bool, err error) {
+ return false, ErrNotImplemented
+}
+
+// Remove not implemented only used in order to implement the NM interface
+func (nmi *mockNMInterface) Remove(path PathItems) (err error) {
+ return ErrNotImplemented
+}
+
+// Type returns the type of the NM interface
+func (nmi *mockNMInterface) Type() NMType {
+ return NMDataType
+}
+
+// Empty returns true if the NM is empty(no data)
+func (nmi *mockNMInterface) Empty() bool {
+ return nmi == nil || nmi.data == nil
+}
+
+// GetField not implemented only used in order to implement the NM interface
+func (nmi *mockNMInterface) GetField(path PathItem) (val NMInterface, err error) {
+ return nil, ErrNotImplemented
+}
+
+// SetField not implemented
+func (nmi *mockNMInterface) SetField(path PathItem, val NMInterface) (err error) {
+ return ErrNotImplemented
+}
+
+// Len not implemented only used in order to implement the NM interface
+func (nmi *mockNMInterface) Len() int {
+ return 0
+}
diff --git a/utils/errors.go b/utils/errors.go
index b30559f01..a779098c1 100644
--- a/utils/errors.go
+++ b/utils/errors.go
@@ -72,6 +72,7 @@ var (
ErrNoDatabaseConn = errors.New("NO_DATA_BASE_CONNECTION")
ErrMaxIncrementsExceeded = errors.New("MAX_INCREMENTS_EXCEEDED")
ErrIndexOutOfBounds = errors.New("INDEX_OUT_OF_BOUNDS")
+ ErrWrongPath = errors.New("WRONG_PATH")
ErrMap = map[string]error{
ErrNoMoreData.Error(): ErrNoMoreData,
@@ -111,6 +112,7 @@ var (
ErrNoDatabaseConn.Error(): ErrNoDatabaseConn,
ErrMaxIncrementsExceeded.Error(): ErrMaxIncrementsExceeded,
ErrIndexOutOfBounds.Error(): ErrIndexOutOfBounds,
+ ErrWrongPath.Error(): ErrWrongPath,
}
)
diff --git a/utils/navigablemap.go b/utils/navigablemap.go
new file mode 100644
index 000000000..083a35860
--- /dev/null
+++ b/utils/navigablemap.go
@@ -0,0 +1,199 @@
+/*
+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 (
+ "net"
+)
+
+// NavigableMap2 is the basic map of NM interface
+type NavigableMap2 map[string]NMInterface
+
+func (nm NavigableMap2) String() (out string) {
+ for k, v := range nm {
+ out += ",\"" + k + "\":" + v.String()
+ }
+ if len(out) == 0 {
+ return "{}"
+ }
+ out = out[1:]
+ return "{" + out + "}"
+}
+
+// Interface returns itself
+func (nm NavigableMap2) Interface() interface{} {
+ return nm
+}
+
+// Field returns the item on the given path
+func (nm NavigableMap2) Field(path PathItems) (val NMInterface, err error) {
+ if len(path) == 0 {
+ return nil, ErrWrongPath
+ }
+ el, has := nm[path[0].Field]
+ if !has {
+ return nil, ErrNotFound
+ }
+ if len(path) == 1 && path[0].Index == nil {
+ return el, nil
+ }
+ switch el.Type() {
+ default:
+ return nil, ErrNotFound
+ case NMMapType:
+ if path[0].Index != nil {
+ return nil, ErrNotFound
+ }
+ return el.Field(path[1:])
+ case NMSliceType:
+ return el.Field(path)
+ }
+}
+
+// Set sets the value for the given path
+func (nm NavigableMap2) Set(path PathItems, val NMInterface) (added bool, err error) {
+ if len(path) == 0 {
+ return false, ErrWrongPath
+ }
+ nmItm, has := nm[path[0].Field]
+
+ if path[0].Index != nil { // has index, should be a slice which is kinda part of our map, hence separate handling
+ if !has {
+ nmItm = &NMSlice{}
+ if _, err = nmItm.Set(path, val); err != nil {
+ return
+ }
+ nm[path[0].Field] = nmItm
+ added = true
+ return
+ }
+ if nmItm.Type() != NMSliceType {
+ return false, ErrWrongPath
+ }
+ return nmItm.Set(path, val)
+ }
+
+ // standard handling
+ if len(path) == 1 { // always overwrite for single path
+ nm[path[0].Field] = val
+ if !has {
+ added = true
+ }
+ return
+ }
+ // from here we should deal only with navmaps due to multiple path
+ if !has {
+ nmItm = NavigableMap2{}
+ nm[path[0].Field] = nmItm
+ }
+ if nmItm.Type() != NMMapType { // do not try to overwrite an interface
+ return false, ErrWrongPath
+ }
+ return nmItm.Set(path[1:], val)
+}
+
+// Remove removes the item for the given path
+func (nm NavigableMap2) Remove(path PathItems) (err error) {
+ if len(path) == 0 {
+ return ErrWrongPath
+ }
+ el, has := nm[path[0].Field]
+ if !has {
+ return // already removed
+ }
+ if len(path) == 1 {
+ if path[0].Index != nil {
+ if el.Type() != NMSliceType {
+ return ErrWrongPath
+ }
+ // this should not return error
+ // but in case it does we propagate it further
+ err = el.Remove(path)
+ if el.Empty() {
+ delete(nm, path[0].Field)
+ }
+ return
+ }
+ delete(nm, path[0].Field)
+ return
+ }
+ if path[0].Index != nil {
+ if el.Type() != NMSliceType {
+ return ErrWrongPath
+ }
+ if err = el.Remove(path); err != nil {
+ return
+ }
+ if el.Empty() {
+ delete(nm, path[0].Field)
+ }
+ return
+ }
+ if el.Type() != NMMapType {
+ return ErrWrongPath
+ }
+ if err = el.Remove(path[1:]); err != nil {
+ return
+ }
+ if el.Empty() {
+ delete(nm, path[0].Field)
+ }
+ return
+}
+
+// Type returns the type of the NM map
+func (nm NavigableMap2) Type() NMType {
+ return NMMapType
+}
+
+// Empty returns true if the NM is empty(no data)
+func (nm NavigableMap2) Empty() bool {
+ return nm == nil || len(nm) == 0
+}
+
+// Len returns the lenght of the map
+func (nm NavigableMap2) Len() int {
+ return len(nm)
+}
+
+// FieldAsInterface returns the interface at the path
+// Is used by AgentRequest FieldAsInterface
+func (nm NavigableMap2) FieldAsInterface(fldPath []string) (str interface{}, err error) {
+ var nmi NMInterface
+ if nmi, err = nm.Field(NewPathToItem(fldPath)); err != nil {
+ return
+ }
+ return nmi.Interface(), nil
+}
+
+// FieldAsString returns the string at the path
+// Used only to implement the DataProvider interface
+func (nm NavigableMap2) FieldAsString(fldPath []string) (str string, err error) {
+ var val interface{}
+ val, err = nm.FieldAsInterface(fldPath)
+ if err != nil {
+ return
+ }
+ return IfaceAsString(val), nil
+}
+
+// RemoteHost is part of dataStorage interface
+func (NavigableMap2) RemoteHost() net.Addr {
+ return LocalAddr()
+}
diff --git a/utils/navigablemap_test.go b/utils/navigablemap_test.go
new file mode 100644
index 000000000..4619a1193
--- /dev/null
+++ b/utils/navigablemap_test.go
@@ -0,0 +1,440 @@
+/*
+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 (
+ "reflect"
+ "testing"
+)
+
+func TestNavigableMap2String(t *testing.T) {
+ var nm NMInterface = NavigableMap2{"Field1": NewNMData("1001")}
+ expected := `{"Field1":1001}`
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+ nm = NavigableMap2{}
+ expected = `{}`
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
+
+func TestNavigableMap2Interface(t *testing.T) {
+ nm := NavigableMap2{"Field1": NewNMData("1001"), "Field2": NewNMData("1003")}
+ expected := NavigableMap2{"Field1": NewNMData("1001"), "Field2": NewNMData("1003")}
+ if rply := nm.Interface(); !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %s ,received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
+
+func TestNavigableMap2Field(t *testing.T) {
+ nm := NavigableMap2{}
+ if _, err := nm.Field(PathItems{{}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ nm = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if _, err := nm.Field(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Field(PathItems{{Field: "NaN"}}); err != ErrNotFound {
+ t.Error(err)
+ }
+
+ if val, err := nm.Field(PathItems{{Field: "Field1"}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "1001" {
+ t.Errorf("Expected %q ,received: %q", "1001", val.Interface())
+ }
+
+ if _, err := nm.Field(PathItems{{Field: "Field1", Index: IntPointer(0)}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if val, err := nm.Field(PathItems{{Field: "Field5", Index: IntPointer(0)}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val.Interface())
+ }
+ if _, err := nm.Field(PathItems{{Field: "Field3", Index: IntPointer(0)}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if val, err := nm.Field(PathItems{{Field: "Field3"}, {Field: "Field4"}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val.Interface())
+ }
+}
+
+func TestNavigableMap2Set(t *testing.T) {
+ nm := NavigableMap2{}
+ if _, err := nm.Set(nil, nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(10)}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected := NavigableMap2{"Field1": &NMSlice{NewNMData("1001")}}
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(0)}}, NewNMData("1001")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001")},
+ "Field2": NewNMData("1002"),
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field2"}}, NewNMData("1002")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field2", Index: IntPointer(1)}}, NewNMData("1003")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003")},
+ "Field2": NewNMData("1002"),
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(1)}}, NewNMData("1003")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003")},
+ "Field2": NewNMData("1004"),
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field2"}}, NewNMData("1004")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if _, err := nm.Set(PathItems{{Field: "Field3", Index: IntPointer(10)}, {}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003")},
+ "Field2": NewNMData("1004"),
+ "Field3": &NMSlice{NavigableMap2{"Field4": NewNMData("1005")}},
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field3", Index: IntPointer(0)}, {Field: "Field4"}}, NewNMData("1005")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if _, err := nm.Set(PathItems{{Field: "Field5"}, {Field: "Field6", Index: IntPointer(10)}}, NewNMData("1006")); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003")},
+ "Field2": NewNMData("1004"),
+ "Field3": &NMSlice{NavigableMap2{"Field4": NewNMData("1005")}},
+ "Field5": NavigableMap2{"Field6": NewNMData("1006")},
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field5"}, {Field: "Field6"}}, NewNMData("1006")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if _, err := nm.Set(PathItems{{Field: "Field2", Index: IntPointer(0)}, {}}, NewNMData("1006")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003"), NavigableMap2{"Field6": NewNMData("1006")}},
+ "Field2": NewNMData("1004"),
+ "Field3": &NMSlice{NavigableMap2{"Field4": NewNMData("1005")}},
+ "Field5": NavigableMap2{"Field6": NewNMData("1006")},
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(2)}, {Field: "Field6"}}, NewNMData("1006")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field2"}, {}}, NewNMData("1006")); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ expected = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1001"), NewNMData("1003"), NavigableMap2{"Field6": NewNMData("1006")}},
+ "Field2": NewNMData("1004"),
+ "Field3": &NMSlice{NavigableMap2{"Field4": NewNMData("1005")}},
+ "Field5": NavigableMap2{"Field6": NewNMData("1007")},
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field5"}, {Field: "Field6"}}, NewNMData("1007")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+}
+
+func TestNavigableMap2Type(t *testing.T) {
+ var nm NMInterface = NavigableMap2{}
+ if nm.Type() != NMMapType {
+ t.Errorf("Expected %v ,received: %v", NMMapType, nm.Type())
+ }
+}
+
+func TestNavigableMap2Empty(t *testing.T) {
+ var nm NMInterface = NavigableMap2{}
+ if !nm.Empty() {
+ t.Error("Expected empty type")
+ }
+ nm = NavigableMap2{"Field1": NewNMData("1001")}
+ if nm.Empty() {
+ t.Error("Expected not empty type")
+ }
+}
+
+func TestNavigableMap2Len(t *testing.T) {
+ var nm NMInterface = NavigableMap2{}
+ if rply := nm.Len(); rply != 0 {
+ t.Errorf("Expected 0 ,received: %v", rply)
+ }
+ nm = NavigableMap2{"Field1": NewNMData("1001")}
+ if rply := nm.Len(); rply != 1 {
+ t.Errorf("Expected 1 ,received: %v", rply)
+ }
+}
+
+func TestNavigableMap2Remove(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if err := nm.Remove(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(PathItems{}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(PathItems{{Field: "field"}}); err != nil {
+ t.Error(err)
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(-1)}, {}}); err != nil {
+ t.Error(err)
+ }
+ expected := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ expected = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field2"}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ expected = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(101)},
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field5", Index: IntPointer(0)}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field1", Index: IntPointer(0)}, {}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ expected = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field5", Index: IntPointer(0)}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ nm = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NavigableMap2{"Field42": NewNMData("Val2")}},
+ }
+ if err := nm.Remove(PathItems{{Field: "Field5", Index: IntPointer(0)}, {Field: "Field42", Index: IntPointer(0)}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ expected = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field5", Index: IntPointer(0)}, {Field: "Field42"}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if err := nm.Remove(PathItems{{Field: "Field1"}, {}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(PathItems{{Field: "Field3"}, {Field: "Field4", Index: IntPointer(0)}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ }
+ if err := nm.Remove(PathItems{{Field: "Field3"}, {Field: "Field4"}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+}
+
+func TestNavigableMap2GetSet(t *testing.T) {
+ var nm NMInterface = NavigableMap2{
+ "Field1": NewNMData(10),
+ "Field2": &NMSlice{
+ NewNMData("1001"),
+ NavigableMap2{
+ "Account": &NMSlice{NewNMData(10), NewNMData(11)},
+ },
+ },
+ "Field3": NavigableMap2{
+ "Field4": NavigableMap2{
+ "Field5": NewNMData(5),
+ },
+ },
+ }
+ path := PathItems{{Field: "Field1"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val.Interface())
+ }
+
+ path = PathItems{{Field: "Field3"}, {Field: "Field4"}, {Field: "Field5"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 5 {
+ t.Errorf("Expected %q ,received: %q", 5, val.Interface())
+ }
+
+ path = PathItems{{Field: "Field2", Index: IntPointer(2)}}
+ if _, err := nm.Set(path, NewNMData("500")); err != nil {
+ t.Error(err)
+ }
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "500" {
+ t.Errorf("Expected %q ,received: %q", "500", val.Interface())
+ }
+
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account"}}
+ if _, err := nm.Set(path, NewNMData("5")); err != nil {
+ t.Error(err)
+ }
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "5" {
+ t.Errorf("Expected %q ,received: %q", "5", val.Interface())
+ }
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account", Index: IntPointer(0)}}
+ if _, err := nm.Field(path); err != ErrNotFound {
+ t.Error(err)
+ }
+}
+
+func TestNavigableMap2FieldAsInterface(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if _, err := nm.FieldAsInterface(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if val, err := nm.FieldAsInterface([]string{"Field3", "Field4"}); err != nil {
+ t.Error(err)
+ } else if val != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val)
+ }
+
+ if val, err := nm.FieldAsInterface([]string{"Field5[0]"}); err != nil {
+ t.Error(err)
+ } else if val != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val)
+ }
+}
+
+func TestNavigableMap2FieldAsString(t *testing.T) {
+ nm := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ if _, err := nm.FieldAsString(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if val, err := nm.FieldAsString([]string{"Field3", "Field4"}); err != nil {
+ t.Error(err)
+ } else if val != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val)
+ }
+
+ if val, err := nm.FieldAsString([]string{"Field5[0]"}); err != nil {
+ t.Error(err)
+ } else if val != "10" {
+ t.Errorf("Expected %q ,received: %q", "10", val)
+ }
+}
+
+func TestNavigableMapRemote(t *testing.T) {
+ nm := NavigableMap2{"Field1": NewNMData("1001")}
+ eOut := LocalAddr()
+ if rcv := nm.RemoteHost(); !reflect.DeepEqual(eOut, rcv) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, rcv)
+ }
+}
diff --git a/utils/nmdata.go b/utils/nmdata.go
new file mode 100644
index 000000000..a96338714
--- /dev/null
+++ b/utils/nmdata.go
@@ -0,0 +1,70 @@
+/*
+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
+
+// NewNMData returns the interface wraped in NMInterface struture
+func NewNMData(val interface{}) *NMData { return &NMData{data: val} }
+
+// NMData most basic NM structure
+type NMData struct{ data interface{} }
+
+func (nmi *NMData) String() string {
+ return IfaceAsString(nmi.data)
+}
+
+// Interface returns the wraped interface
+func (nmi *NMData) Interface() interface{} {
+ return nmi.data
+}
+
+// Field is not implemented only used in order to implement the NM interface
+func (nmi *NMData) Field(path PathItems) (val NMInterface, err error) {
+ return nil, ErrNotImplemented
+}
+
+// Set sets the wraped interface when the path is empty
+// This behaivior is in order to modify the wraped interface
+// witout aserting the type of the NMInterface
+func (nmi *NMData) Set(path PathItems, val NMInterface) (addedNew bool, err error) {
+ if len(path) != 0 {
+ return false, ErrWrongPath
+ }
+ nmi.data = val.Interface()
+ return
+}
+
+// Remove is not implemented only used in order to implement the NM interface
+func (nmi *NMData) Remove(path PathItems) (err error) {
+ return ErrNotImplemented
+}
+
+// Type returns the type of the NM interface
+func (nmi *NMData) Type() NMType {
+ return NMDataType
+}
+
+// Empty returns true if the NM is empty(no data)
+func (nmi *NMData) Empty() bool {
+ return nmi == nil || nmi.data == nil
+}
+
+// Len is not implemented only used in order to implement the NM interface
+func (nmi *NMData) Len() int {
+ return 0
+}
diff --git a/utils/nmdata_test.go b/utils/nmdata_test.go
new file mode 100644
index 000000000..be54cc92e
--- /dev/null
+++ b/utils/nmdata_test.go
@@ -0,0 +1,109 @@
+/*
+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 (
+ "reflect"
+ "testing"
+)
+
+func TestNewNMInterface(t *testing.T) {
+ nm2 := NewNMData("1001")
+ expectednm := &NMData{data: "1001"}
+ if !reflect.DeepEqual(expectednm, nm2) {
+ t.Errorf("Expected %v ,received: %v", ToJSON(expectednm), ToJSON(nm2))
+ }
+ var nm NMInterface = nm2
+ expected := "1001"
+ if rply := nm.Interface(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+ if nm2.data != expected {
+ t.Errorf("Expected %q ,received: %q", expected, nm2.data)
+ }
+}
+
+func TestNMDataLen(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if rply := nm.Len(); rply != 0 {
+ t.Errorf("Expected 0 ,received: %v", rply)
+ }
+}
+
+func TestNMDataString(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ expected := "1001"
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
+
+func TestNMDataInterface(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ expected := "1001"
+ if rply := nm.Interface(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
+
+func TestNMDataField(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if _, err := nm.Field(nil); err != ErrNotImplemented {
+ t.Error(err)
+ }
+}
+
+func TestNMDataRemove(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if err := nm.Remove(nil); err != ErrNotImplemented {
+ t.Error(err)
+ }
+}
+
+func TestNMDataEmpty(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if nm.Empty() {
+ t.Error("Expected not empty type")
+ }
+ nm = NewNMData(nil)
+ if !nm.Empty() {
+ t.Error("Expected empty type")
+ }
+}
+
+func TestNMDataType(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if nm.Type() != NMDataType {
+ t.Errorf("Expected %v ,received: %v", NMDataType, nm.Type())
+ }
+}
+
+func TestNMDataSet(t *testing.T) {
+ var nm NMInterface = NewNMData("1001")
+ if _, err := nm.Set(PathItems{{}}, nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(nil, NewNMData("1002")); err != nil {
+ t.Error(err)
+ }
+ expected := "1002"
+ if rply := nm.Interface(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
diff --git a/utils/nmslice.go b/utils/nmslice.go
new file mode 100644
index 000000000..e7f6bff6b
--- /dev/null
+++ b/utils/nmslice.go
@@ -0,0 +1,140 @@
+/*
+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
+
+// NMSlice is the basic slice of NM interface
+type NMSlice []NMInterface
+
+func (nms *NMSlice) String() (out string) {
+ for _, v := range *nms {
+ out += "," + v.String()
+ }
+ if len(out) == 0 {
+ return "[]"
+ }
+ out = out[1:]
+ return "[" + out + "]"
+}
+
+// Interface returns itself
+func (nms *NMSlice) Interface() interface{} {
+ return nms
+}
+
+// Field returns the item on the given path
+// for NMSlice only the Index field is considered
+func (nms *NMSlice) Field(path PathItems) (val NMInterface, err error) {
+ if len(path) == 0 {
+ return nil, ErrWrongPath
+ }
+ if nms.Empty() || path[0].Index == nil {
+ return nil, ErrNotFound
+ }
+ idx := *path[0].Index
+ if idx < 0 {
+ idx = len(*nms) + idx
+ }
+ if idx < 0 || idx >= len(*nms) {
+ return nil, ErrNotFound
+ }
+ if len(path) == 1 {
+ return (*nms)[idx], nil
+ }
+ return (*nms)[idx].Field(path[1:])
+}
+
+// Set sets the value for the given index
+func (nms *NMSlice) Set(path PathItems, val NMInterface) (addedNew bool, err error) {
+ if len(path) == 0 || path[0].Index == nil {
+ return false, ErrWrongPath
+ }
+ idx := *path[0].Index
+ if idx == len(*nms) { // append element
+ addedNew = true
+ if len(path) == 1 {
+ *nms = append(*nms, val)
+ return
+ }
+ nel := NavigableMap2{}
+ if _, err = nel.Set(path[1:], val); err != nil {
+ return
+ }
+ *nms = append(*nms, nel)
+ return
+ }
+ if idx < 0 {
+ idx = len(*nms) + idx
+ }
+ if idx < 0 || idx >= len(*nms) {
+ return false, ErrWrongPath
+ }
+ path[0].Index = &idx
+ if len(path) == 1 {
+ (*nms)[idx] = val
+ return
+ }
+ if (*nms)[idx].Type() == NMSliceType {
+ return false, ErrWrongPath
+ }
+ return (*nms)[idx].Set(path[1:], val)
+}
+
+// Remove removes the item for the given index
+func (nms *NMSlice) Remove(path PathItems) (err error) {
+ if len(path) == 0 || path[0].Index == nil {
+ return ErrWrongPath
+ }
+ idx := *path[0].Index
+ if idx < 0 {
+ idx = len(*nms) + idx
+ }
+ if idx < 0 || idx >= len(*nms) { // already removed
+ return
+ }
+ path[0].Index = &idx
+ if len(path) == 1 {
+ *nms = append((*nms)[:idx], (*nms)[idx+1:]...)
+ return
+ }
+ if (*nms)[idx].Type() != NMMapType {
+ return ErrWrongPath
+ }
+ if err = (*nms)[idx].Remove(path[1:]); err != nil {
+ return
+ }
+ if (*nms)[idx].Empty() {
+ *nms = append((*nms)[:idx], (*nms)[idx+1:]...)
+ }
+ return
+}
+
+// Type returns the type of the NM slice
+func (nms NMSlice) Type() NMType {
+ return NMSliceType
+}
+
+// Empty returns true if the NM is empty(no data)
+func (nms NMSlice) Empty() bool {
+ return nms == nil || len(nms) == 0
+}
+
+// Len returns the lenght of the slice
+func (nms *NMSlice) Len() int {
+ return len(*nms)
+}
diff --git a/utils/nmslice_test.go b/utils/nmslice_test.go
new file mode 100644
index 000000000..65564d07e
--- /dev/null
+++ b/utils/nmslice_test.go
@@ -0,0 +1,206 @@
+/*
+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 (
+ "reflect"
+ "testing"
+)
+
+func TestNMSliceString(t *testing.T) {
+ var nm NMInterface = &NMSlice{NewNMData("1001"), NewNMData("1003")}
+ expected := "[1001,1003]"
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+ nm = &NMSlice{}
+ expected = `[]`
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
+
+func TestNMSliceInterface(t *testing.T) {
+ nm := &NMSlice{NewNMData("1001"), NewNMData("1003")}
+ expected := &NMSlice{NewNMData("1001"), NewNMData("1003")}
+ if rply := nm.Interface(); !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %s ,received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
+
+func TestNMSliceField(t *testing.T) {
+ nm := &NMSlice{}
+ if _, err := nm.Field(PathItems{{}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ nm = &NMSlice{
+ NewNMData("1001"),
+ NewNMData("1003"),
+ &NavigableMap2{"Field1": NewNMData("Val")},
+ }
+ if _, err := nm.Field(PathItems{{}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if _, err := nm.Field(PathItems{{Index: IntPointer(4)}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if _, err := nm.Field(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if val, err := nm.Field(PathItems{{Field: "None", Index: IntPointer(-1)}, {Field: "Field1"}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val.Interface())
+ }
+ if val, err := nm.Field(PathItems{{Field: "1234", Index: IntPointer(1)}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "1003" {
+ t.Errorf("Expected %q ,received: %q", "Val", val.Interface())
+ }
+}
+
+func TestNMSliceSet(t *testing.T) {
+ nm := &NMSlice{}
+ if _, err := nm.Set(nil, nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected := &NMSlice{NewNMData("1001")}
+ if _, err := nm.Set(PathItems{{Field: "1234", Index: IntPointer(0)}}, NewNMData("1001")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ if _, err := nm.Set(PathItems{{Field: "1234", Index: IntPointer(1)}, {Field: "Field1", Index: IntPointer(1)}},
+ NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = &NMSlice{NewNMData("1001"), NavigableMap2{"Field1": NewNMData("1001")}}
+ if _, err := nm.Set(PathItems{{Field: "1234", Index: IntPointer(1)}, {Field: "Field1"}},
+ NewNMData("1001")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+ expected = &NMSlice{NewNMData("1001"), NewNMData("1001")}
+ if _, err := nm.Set(PathItems{{Field: "1234", Index: IntPointer(-1)}}, NewNMData("1001")); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ nm = &NMSlice{&NMSlice{}}
+ if _, err := nm.Set(PathItems{{Field: "1234", Index: IntPointer(0)}, {}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+}
+
+func TestNMSliceType(t *testing.T) {
+ var nm NMInterface = &NMSlice{}
+ if nm.Type() != NMSliceType {
+ t.Errorf("Expected %v ,received: %v", NMSliceType, nm.Type())
+ }
+}
+
+func TestNMSliceEmpty(t *testing.T) {
+ var nm NMInterface = &NMSlice{}
+ if !nm.Empty() {
+ t.Error("Expected empty type")
+ }
+ nm = &NMSlice{NewNMData("1001")}
+ if nm.Empty() {
+ t.Error("Expected not empty type")
+ }
+}
+
+func TestNMSliceLen(t *testing.T) {
+ var nm NMInterface = &NMSlice{}
+ if rply := nm.Len(); rply != 0 {
+ t.Errorf("Expected 0 ,received: %v", rply)
+ }
+ nm = &NMSlice{NewNMData("1001")}
+ if rply := nm.Len(); rply != 1 {
+ t.Errorf("Expected 1 ,received: %v", rply)
+ }
+}
+
+func TestNMSliceRemove(t *testing.T) {
+ nm := &NMSlice{
+ NewNMData("1001"),
+ NewNMData("1003"),
+ &NavigableMap2{"Field1": NewNMData("Val")},
+ &NMSlice{},
+ }
+ if err := nm.Remove(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(PathItems{}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(PathItems{{Field: "field"}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(-1)}, {}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected := &NMSlice{
+ NewNMData("1001"),
+ NewNMData("1003"),
+ &NavigableMap2{"Field1": NewNMData("Val")},
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(-1)}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(1)}, {}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ expected = &NMSlice{
+ NewNMData("1001"),
+ &NavigableMap2{"Field1": NewNMData("Val")},
+ }
+ if err := nm.Remove(PathItems{{Index: IntPointer(1)}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(10)}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+ if err := nm.Remove(PathItems{{Index: IntPointer(1)}, {Field: "Field1", Index: IntPointer(1)}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ expected = &NMSlice{
+ NewNMData("1001"),
+ }
+ if err := nm.Remove(PathItems{{Index: IntPointer(1)}, {Field: "Field1"}}); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(nm, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, nm)
+ }
+
+}
diff --git a/utils/orderednavigablemap.go b/utils/orderednavigablemap.go
new file mode 100644
index 000000000..92e02d07f
--- /dev/null
+++ b/utils/orderednavigablemap.go
@@ -0,0 +1,184 @@
+/*
+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 (
+ "net"
+ "strings"
+)
+
+// NewOrderedNavigableMap initializates a structure of OrderedNavigableMap with a NavigableMap2
+func NewOrderedNavigableMap() *OrderedNavigableMap {
+ return &OrderedNavigableMap{
+ nm: NavigableMap2{},
+ orderIdx: NewPathItemList(),
+ orderRef: make(map[string][]*PathItemElement),
+ }
+}
+
+// OrderedNavigableMap is the same as NavigableMap2 but keeps the order of fields
+type OrderedNavigableMap struct {
+ nm NMInterface
+ orderIdx *PathItemList
+ orderRef map[string][]*PathItemElement
+}
+
+// String returns the map as json string
+func (onm *OrderedNavigableMap) String() string {
+ return onm.nm.String()
+}
+
+// GetFirstElement returns the first element from the order
+func (onm *OrderedNavigableMap) GetFirstElement() *PathItemElement {
+ return onm.orderIdx.Front()
+}
+
+// Interface returns navigble map that's inside
+func (onm *OrderedNavigableMap) Interface() interface{} {
+ return onm.nm
+}
+
+// Field returns the item on the given path
+func (onm *OrderedNavigableMap) Field(fldPath PathItems) (val NMInterface, err error) {
+ return onm.nm.Field(fldPath)
+}
+
+// Type returns the type of the NM map
+func (onm *OrderedNavigableMap) Type() NMType {
+ return onm.nm.Type()
+}
+
+// Empty returns true if the NM is empty(no data)
+func (onm *OrderedNavigableMap) Empty() bool {
+ return onm.nm.Empty()
+}
+
+// Remove removes the item for the given path and updates the order
+func (onm *OrderedNavigableMap) Remove(fullPath FullPath) (err error) {
+ path := stripIdxFromLastPathElm(fullPath.Path)
+ if path == EmptyString || fullPath.PathItems[len(fullPath.PathItems)-1].Index != nil {
+ return ErrWrongPath
+ }
+ if err = onm.nm.Remove(fullPath.PathItems); err != nil {
+ return
+ }
+
+ for idxPath, slcIdx := range onm.orderRef {
+ if !strings.HasPrefix(idxPath, path) {
+ continue
+ }
+ for _, el := range slcIdx {
+ onm.orderIdx.Remove(el)
+ }
+ delete(onm.orderRef, idxPath)
+ }
+ return
+}
+
+// Set sets the value at the given path
+// this is the old to be capable of building the code without updating all the code
+// will be replaced with Set2 after we decide that is the optimal solution
+func (onm *OrderedNavigableMap) Set(fldPath PathItems, val NMInterface) (addedNew bool, err error) {
+ return onm.Set2(&FullPath{PathItems: fldPath, Path: fldPath.String()}, val)
+}
+
+// Set2 sets the value at the given path
+// this used with full path and the processed path to not calculate them for every set
+func (onm *OrderedNavigableMap) Set2(fullPath *FullPath, val NMInterface) (addedNew bool, err error) {
+ if len(fullPath.PathItems) == 0 {
+ return false, ErrWrongPath
+ }
+ if addedNew, err = onm.nm.Set(fullPath.PathItems, val); err != nil {
+ return
+ }
+
+ var pathItmsSet []PathItems // can be multiples if we need to inflate due to missing Index in slice set
+ var nonIndexedSlcPath bool
+ if val.Type() == NMSliceType && fullPath.PathItems[len(fullPath.PathItems)-1].Index == nil { // special case when we overwrite with a slice without specifying indexes
+ nonIndexedSlcPath = true
+ pathItmsSet = make([]PathItems, len(*val.(*NMSlice)))
+ for i := 0; i < val.Len(); i++ {
+ pathItms := fullPath.PathItems.Clone()
+ pathItms[len(pathItms)-1].Index = IntPointer(i)
+ pathItmsSet[i] = pathItms
+ }
+ } else {
+ pathItmsSet = []PathItems{fullPath.PathItems}
+ }
+ path := stripIdxFromLastPathElm(fullPath.Path)
+ if !addedNew && nonIndexedSlcPath { // cleanup old references since the value is being overwritten
+ for idxPath, slcIdx := range onm.orderRef {
+ if !strings.HasPrefix(idxPath, path) {
+ continue
+ }
+ for _, el := range slcIdx {
+ onm.orderIdx.Remove(el)
+ }
+ delete(onm.orderRef, idxPath)
+ }
+ }
+ _, hasRef := onm.orderRef[path]
+ for _, pathItms := range pathItmsSet {
+ if addedNew || !hasRef {
+ onm.orderRef[path] = append(onm.orderRef[path], onm.orderIdx.PushBack(pathItms))
+ } else {
+ onm.orderIdx.MoveToBack(onm.orderRef[path][len(onm.orderRef[path])-1])
+ }
+ }
+ return
+}
+
+// Len returns the lenght of the map
+func (onm OrderedNavigableMap) Len() int {
+ return onm.nm.Len()
+}
+
+// FieldAsString returns thevalue from path as string
+func (onm *OrderedNavigableMap) FieldAsString(fldPath []string) (str string, err error) {
+ var val NMInterface
+ val, err = onm.nm.Field(NewPathToItem(fldPath))
+ if err != nil {
+ return
+ }
+ return IfaceAsString(val.Interface()), nil
+}
+
+// FieldAsInterface returns the interface at the path
+func (onm *OrderedNavigableMap) FieldAsInterface(fldPath []string) (str interface{}, err error) {
+ var val NMInterface
+ val, err = onm.nm.Field(NewPathToItem(fldPath))
+ if err != nil {
+ return
+ }
+ return val.Interface(), nil
+}
+
+// RemoteHost is part of dataStorage interface
+func (OrderedNavigableMap) RemoteHost() net.Addr {
+ return LocalAddr()
+}
+
+// GetOrder returns the elements order as a slice
+// use this only for testing
+func (onm *OrderedNavigableMap) GetOrder() (order []PathItems) {
+ for el := onm.GetFirstElement(); el != nil; el = el.Next() {
+ order = append(order, el.Value)
+ }
+ return
+}
diff --git a/utils/orderednavigablemap_test.go b/utils/orderednavigablemap_test.go
new file mode 100644
index 000000000..f51e68a03
--- /dev/null
+++ b/utils/orderednavigablemap_test.go
@@ -0,0 +1,885 @@
+/*
+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 (
+ "math/rand"
+ "reflect"
+ "testing"
+)
+
+func TestOrderedNavigableMap(t *testing.T) {
+ onm := NewOrderedNavigableMap()
+
+ onm.Set(PathItems{{Field: "Field1"}}, NewNMData(10))
+ expOrder := []PathItems{
+ PathItems{{Field: "Field1"}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ onm.Set(
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ NewNMData("1001"))
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ onm.Set(
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(0)}},
+ NewNMData(10))
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(0)}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ onm.Set(
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(1)}},
+ NewNMData(11))
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(1)}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ onm.Set(
+ PathItems{{Field: "Field2", Index: IntPointer(2)}},
+ NewNMData(111))
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(1)}},
+ PathItems{{Field: "Field2", Index: IntPointer(2)}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ onm.Set(
+ PathItems{
+ {Field: "Field3"},
+ {Field: "Field4"},
+ {Field: "Field5"}},
+ NewNMData(5))
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(0)}},
+ PathItems{
+ {Field: "Field2", Index: IntPointer(1)},
+ {Field: "Account", Index: IntPointer(1)}},
+ PathItems{{Field: "Field2", Index: IntPointer(2)}},
+ PathItems{
+ {Field: "Field3"},
+ {Field: "Field4"},
+ {Field: "Field5"}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, ToJSON(onm.GetOrder()))
+ }
+
+ var expnm NMInterface = NavigableMap2{
+ "Field1": NewNMData(10),
+ "Field2": &NMSlice{
+ NewNMData("1001"),
+ NavigableMap2{
+ "Account": &NMSlice{NewNMData(10), NewNMData(11)},
+ },
+ NewNMData(111),
+ },
+ "Field3": NavigableMap2{
+ "Field4": NavigableMap2{
+ "Field5": NewNMData(5),
+ },
+ },
+ }
+ if onm.Empty() {
+ t.Error("Expected not empty type")
+ }
+ if onm.Type() != NMMapType {
+ t.Errorf("Expected %v ,received: %v", NMDataType, onm.Type())
+ }
+ if !reflect.DeepEqual(expnm, onm.nm) {
+ t.Errorf("Expected %s ,received: %s", expnm, onm.nm)
+ }
+
+ // sliceDeNM
+ exp := &NMSlice{NewNMData("500"), NewNMData("502")}
+ path := PathItems{{Field: "Field2"}}
+ if _, err := onm.Set(path, exp); err != nil {
+ t.Error(err)
+ }
+ path = PathItems{{Field: "Field2"}}
+ if val, err := onm.Field(path); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(val, exp) {
+ t.Errorf("Expected %q ,received: %q", exp, val.Interface())
+ }
+ expOrder = []PathItems{
+ PathItems{{Field: "Field1"}},
+ PathItems{
+ {Field: "Field3"},
+ {Field: "Field4"},
+ {Field: "Field5"}},
+ PathItems{{Field: "Field2", Index: IntPointer(0)}},
+ PathItems{{Field: "Field2", Index: IntPointer(1)}},
+ }
+ if !reflect.DeepEqual(expOrder, onm.GetOrder()) {
+ t.Errorf("Expected %s ,received: %s", expOrder, onm.GetOrder())
+ }
+
+ path = PathItems{{Field: "Field2", Index: IntPointer(0)}}
+ if val, err := onm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "500" {
+ t.Errorf("Expected %q ,received: %q", "500", val.Interface())
+ }
+ expnm = NavigableMap2{
+ "Field1": NewNMData(10),
+ "Field3": NavigableMap2{
+ "Field4": NavigableMap2{
+ "Field5": NewNMData(5),
+ },
+ },
+ "Field2": &NMSlice{
+ NewNMData("500"),
+ NewNMData("502"),
+ },
+ }
+ if !reflect.DeepEqual(expnm, onm.nm) {
+ t.Errorf("Expected %s ,received: %s", expnm, onm.nm)
+ }
+}
+
+func TestOrderedNavigableMapString(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{"Field1": NewNMData("1001")}}
+ expected := `{"Field1":1001}`
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+ nm = &OrderedNavigableMap{nm: NavigableMap2{}}
+ expected = `{}`
+ if rply := nm.String(); rply != expected {
+ t.Errorf("Expected %q ,received: %q", expected, rply)
+ }
+}
+
+func TestOrderedNavigableMapInterface(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{"Field1": NewNMData("1001"), "Field2": NewNMData("1003")}}
+ expected := NavigableMap2{"Field1": NewNMData("1001"), "Field2": NewNMData("1003")}
+ if rply := nm.Interface(); !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected %s ,received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
+
+func TestOrderedNavigableMapField(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{}}
+ if _, err := nm.Field(PathItems{{}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ nm = &OrderedNavigableMap{nm: NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }}
+ if _, err := nm.Field(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Field(PathItems{{Field: "NaN"}}); err != ErrNotFound {
+ t.Error(err)
+ }
+
+ if val, err := nm.Field(PathItems{{Field: "Field1"}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "1001" {
+ t.Errorf("Expected %q ,received: %q", "1001", val.Interface())
+ }
+
+ if _, err := nm.Field(PathItems{{Field: "Field1", Index: IntPointer(0)}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if val, err := nm.Field(PathItems{{Field: "Field5", Index: IntPointer(0)}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val.Interface())
+ }
+ if _, err := nm.Field(PathItems{{Field: "Field3", Index: IntPointer(0)}}); err != ErrNotFound {
+ t.Error(err)
+ }
+ if val, err := nm.Field(PathItems{{Field: "Field3"}, {Field: "Field4"}}); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val.Interface())
+ }
+}
+
+func TestOrderedNavigableMapType(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{}}
+ if nm.Type() != NMMapType {
+ t.Errorf("Expected %v ,received: %v", NMMapType, nm.Type())
+ }
+}
+
+func TestOrderedNavigableMapEmpty(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{}}
+ if !nm.Empty() {
+ t.Error("Expected empty type")
+ }
+ nm = &OrderedNavigableMap{nm: NavigableMap2{"Field1": NewNMData("1001")}}
+ if nm.Empty() {
+ t.Error("Expected not empty type")
+ }
+}
+
+func TestOrderedNavigableMapLen(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{}}
+ if rply := nm.Len(); rply != 0 {
+ t.Errorf("Expected 0 ,received: %v", rply)
+ }
+ nm = &OrderedNavigableMap{nm: NavigableMap2{"Field1": NewNMData("1001")}}
+ if rply := nm.Len(); rply != 1 {
+ t.Errorf("Expected 1 ,received: %v", rply)
+ }
+}
+
+func TestOrderedNavigableMapGetSet(t *testing.T) {
+ nm := NewOrderedNavigableMap()
+ nm.Set2(&FullPath{
+ PathItems: PathItems{{Field: "Account", Index: IntPointer(0)}},
+ Path: "Account",
+ }, NewNMData(1001))
+ nm.Set2(&FullPath{
+ PathItems: PathItems{{Field: "Account", Index: IntPointer(1)}},
+ Path: "Account",
+ }, NewNMData("account_on_new_branch"))
+
+ expectedOrder := []PathItems{
+ {{Field: "Account", Index: IntPointer(0)}},
+ {{Field: "Account", Index: IntPointer(1)}},
+ }
+
+ if recivedOrder := nm.GetOrder(); !reflect.DeepEqual(expectedOrder, recivedOrder) {
+ t.Errorf("Expected %s ,received: %s", expectedOrder, recivedOrder)
+ }
+ nm = &OrderedNavigableMap{
+ nm: NavigableMap2{
+ "Field1": NewNMData(10),
+ "Field2": &NMSlice{
+ NewNMData("1001"),
+ NavigableMap2{
+ "Account": &NMSlice{NewNMData(10), NewNMData(11)},
+ },
+ },
+ "Field3": NavigableMap2{
+ "Field4": NavigableMap2{
+ "Field5": NewNMData(5),
+ },
+ },
+ },
+ orderIdx: NewPathItemList(),
+ orderRef: make(map[string][]*PathItemElement),
+ }
+ path := PathItems{{Field: "Field1"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val.Interface())
+ }
+
+ path = PathItems{{Field: "Field3"}, {Field: "Field4"}, {Field: "Field5"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != 5 {
+ t.Errorf("Expected %q ,received: %q", 5, val.Interface())
+ }
+
+ path = PathItems{{Field: "Field2", Index: IntPointer(2)}}
+ if _, err := nm.Set(path, NewNMData("500")); err != nil {
+ t.Error(err)
+ }
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "500" {
+ t.Errorf("Expected %q ,received: %q", "500", val.Interface())
+ }
+
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account"}}
+ if _, err := nm.Set(path, NewNMData("5")); err != nil {
+ t.Error(err)
+ }
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account"}}
+ if val, err := nm.Field(path); err != nil {
+ t.Error(err)
+ } else if val.Interface() != "5" {
+ t.Errorf("Expected %q ,received: %q", "5", val.Interface())
+ }
+ path = PathItems{{Field: "Field2", Index: IntPointer(1)}, {Field: "Account", Index: IntPointer(0)}}
+ if _, err := nm.Field(path); err != ErrNotFound {
+ t.Error(err)
+ }
+}
+
+func TestOrderedNavigableMapFieldAsInterface(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }}
+ if _, err := nm.FieldAsInterface(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if val, err := nm.FieldAsInterface([]string{"Field3", "Field4"}); err != nil {
+ t.Error(err)
+ } else if val != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val)
+ }
+
+ if val, err := nm.FieldAsInterface([]string{"Field5[0]"}); err != nil {
+ t.Error(err)
+ } else if val != 10 {
+ t.Errorf("Expected %q ,received: %q", 10, val)
+ }
+}
+
+func TestOrderedNavigableMapFieldAsString(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }}
+ if _, err := nm.FieldAsString(nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if val, err := nm.FieldAsString([]string{"Field3", "Field4"}); err != nil {
+ t.Error(err)
+ } else if val != "Val" {
+ t.Errorf("Expected %q ,received: %q", "Val", val)
+ }
+
+ if val, err := nm.FieldAsString([]string{"Field5[0]"}); err != nil {
+ t.Error(err)
+ } else if val != "10" {
+ t.Errorf("Expected %q ,received: %q", 10, val)
+ }
+}
+
+func TestOrderedNavigableMapGetOrder(t *testing.T) {
+ nm := NewOrderedNavigableMap()
+ nm.Set(PathItems{{Field: "Field1"}, {Field: "Field2", Index: IntPointer(0)}}, NewNMData("1003"))
+ nm.Set(PathItems{{Field: "Field1"}, {Field: "Field2", Index: IntPointer(1)}}, NewNMData("Val"))
+ nm.Set(PathItems{{Field: "Field3"}, {Field: "Field4"}, {Field: "Field5", Index: IntPointer(0)}}, NewNMData("1001"))
+ nm.Set(PathItems{{Field: "Field1"}, {Field: "Field2", Index: IntPointer(2)}}, NewNMData(101))
+ expected := []PathItems{
+ {{Field: "Field1"}, {Field: "Field2", Index: IntPointer(0)}},
+ {{Field: "Field1"}, {Field: "Field2", Index: IntPointer(1)}},
+ {{Field: "Field3"}, {Field: "Field4"}, {Field: "Field5", Index: IntPointer(0)}},
+ {{Field: "Field1"}, {Field: "Field2", Index: IntPointer(2)}},
+ }
+ if rply := nm.GetOrder(); !reflect.DeepEqual(rply, expected) {
+ t.Errorf("Expected %s ,received: %s", expected, rply)
+ }
+}
+
+//////////////////////////////////////////
+
+func TestOrderedNavigableMapSet(t *testing.T) {
+ nm := NewOrderedNavigableMap()
+ if _, err := nm.Set(nil, nil); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(10)}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(10)}, {Field: "Field2"}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ path := PathItems{{Field: "Field1", Index: IntPointer(0)}}
+ if addedNew, err := nm.Set(path, NewNMData("1001")); err != nil {
+ t.Error(err)
+ } else if !addedNew {
+ t.Error("Expected the field to be added new")
+ }
+ nMap := NavigableMap2{"Field1": &NMSlice{NewNMData("1001")}}
+ order := []PathItems{path}
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(0)}, {}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(10)}}, NewNMData("1001")); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(0)}, {}}, &NMSlice{}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if _, err := nm.Set(PathItems{{Field: "Field1", Index: IntPointer(10)}}, &NMSlice{}); err != ErrWrongPath {
+ t.Error(err)
+ }
+
+ nMap = NavigableMap2{"Field1": &NMSlice{NewNMData("1002")}}
+ order = []PathItems{path}
+ if addedNew, err := nm.Set(path, NewNMData("1002")); err != nil {
+ t.Error(err)
+ } else if addedNew {
+ t.Error("Expected the field to be only updated")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ path = PathItems{{Field: "Field2"}}
+ nMap = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1002")},
+ "Field2": NewNMData("1002"),
+ }
+ order = append(order, path)
+ if addedNew, err := nm.Set(path, NewNMData("1002")); err != nil {
+ t.Error(err)
+ } else if !addedNew {
+ t.Error("Expected the field to be added new")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ path = PathItems{{Field: "Field1", Index: IntPointer(1)}}
+ nMap = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1002"), NewNMData("1003")},
+ "Field2": NewNMData("1002"),
+ }
+ order = append(order, path)
+ if addedNew, err := nm.Set(path, NewNMData("1003")); err != nil {
+ t.Error(err)
+ } else if !addedNew {
+ t.Error("Expected the field to be added new")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ path = PathItems{{Field: "Field3"}}
+ obj := &NMSlice{NewNMData("1004"), NewNMData("1005")}
+ nMap = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1002"), NewNMData("1003")},
+ "Field2": NewNMData("1002"),
+ "Field3": obj,
+ }
+ order = append(order, PathItems{{Field: "Field3", Index: IntPointer(0)}}, PathItems{{Field: "Field3", Index: IntPointer(1)}})
+ if addedNew, err := nm.Set(path, obj); err != nil {
+ t.Error(err)
+ } else if !addedNew {
+ t.Error("Expected the field to be added new")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ obj = &NMSlice{NewNMData("1005"), NewNMData("1006")}
+ nMap = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1005"), NewNMData("1006")},
+ "Field2": NewNMData("1002"),
+ "Field3": &NMSlice{NewNMData("1004"), NewNMData("1005")},
+ }
+ order = []PathItems{
+ {{Field: "Field2"}},
+ {{Field: "Field3", Index: IntPointer(0)}},
+ {{Field: "Field3", Index: IntPointer(1)}},
+ {{Field: "Field1", Index: IntPointer(0)}},
+ {{Field: "Field1", Index: IntPointer(1)}},
+ }
+ if addedNew, err := nm.Set(PathItems{{Field: "Field1"}}, obj); err != nil {
+ t.Error(err)
+ } else if addedNew {
+ t.Error("Expected the field to be only updated")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ obj = &NMSlice{NewNMData("1005"), NewNMData("1006")}
+ nMap = NavigableMap2{
+ "Field1": &NMSlice{NewNMData("1005"), NewNMData("1006")},
+ "Field2": NewNMData("1002"),
+ "Field3": &NMSlice{NewNMData("1004"), NewNMData("1007")},
+ }
+ order = []PathItems{
+ {{Field: "Field2"}},
+ {{Field: "Field3", Index: IntPointer(0)}},
+ {{Field: "Field1", Index: IntPointer(0)}},
+ {{Field: "Field1", Index: IntPointer(1)}},
+ {{Field: "Field3", Index: IntPointer(1)}},
+ }
+ if addedNew, err := nm.Set(PathItems{{Field: "Field3", Index: IntPointer(-1)}}, NewNMData("1007")); err != nil {
+ t.Error(err)
+ } else if addedNew {
+ t.Error("Expected the field to be only updated")
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+}
+
+func TestOrderedNavigableMapRemove(t *testing.T) {
+ nm := NewOrderedNavigableMap()
+ nm.Set(PathItems{{Field: "Field2"}}, NewNMData("1003"))
+ nm.Set(PathItems{{Field: "Field3"}, {Field: "Field4"}}, NewNMData("Val"))
+ nm.Set(PathItems{{Field: "Field1"}}, NewNMData("1001"))
+ nm.Set(PathItems{{Field: "Field5"}}, &NMSlice{NewNMData(10), NewNMData(101)})
+ if err := nm.Remove(FullPath{}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(FullPath{PathItems: PathItems{}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ if err := nm.Remove(FullPath{PathItems: PathItems{{Field: "field"}}, Path: "field"}); err != nil {
+ t.Error(err)
+ }
+
+ if err := nm.Remove(FullPath{PathItems: PathItems{{Index: IntPointer(-1)}, {}}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+ nMap := NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field2": NewNMData("1003"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ order := []PathItems{
+ {{Field: "Field2"}},
+ {{Field: "Field3"}, {Field: "Field4"}},
+ {{Field: "Field1"}},
+ {{Field: "Field5", Index: IntPointer(0)}},
+ {{Field: "Field5", Index: IntPointer(1)}},
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ nMap = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ "Field5": &NMSlice{NewNMData(10), NewNMData(101)},
+ }
+ order = []PathItems{
+ {{Field: "Field3"}, {Field: "Field4"}},
+ {{Field: "Field1"}},
+ {{Field: "Field5", Index: IntPointer(0)}},
+ {{Field: "Field5", Index: IntPointer(1)}},
+ }
+
+ if err := nm.Remove(FullPath{PathItems: PathItems{{Field: "Field2"}}, Path: "Field2"}); err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ nMap = NavigableMap2{
+ "Field1": NewNMData("1001"),
+ "Field3": NavigableMap2{"Field4": NewNMData("Val")},
+ }
+ order = []PathItems{
+ {{Field: "Field3"}, {Field: "Field4"}},
+ {{Field: "Field1"}},
+ }
+
+ if err := nm.Remove(FullPath{PathItems: PathItems{{Field: "Field5"}}, Path: "Field5"}); err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(nm.nm, nMap) {
+ t.Errorf("Expected %s ,received: %s", nMap, nm)
+ }
+ if !reflect.DeepEqual(nm.GetOrder(), order) {
+ t.Errorf("Expected %s ,received: %s", order, nm.GetOrder())
+ }
+ if err := nm.Remove(FullPath{PathItems: PathItems{{Field: "Field1", Index: IntPointer(0)}, {}}}); err != ErrWrongPath {
+ t.Error(err)
+ }
+}
+
+func TestOrderedNavigableRemote(t *testing.T) {
+ nm := &OrderedNavigableMap{nm: NavigableMap2{"Field1": NewNMData("1001")}}
+ eOut := LocalAddr()
+ if rcv := nm.RemoteHost(); !reflect.DeepEqual(eOut, rcv) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, rcv)
+ }
+}
+
+var generator = rand.New(rand.NewSource(42))
+var gen = generateRandomTemplate(10_000)
+
+type benchData struct {
+ path []string
+ pathItems PathItems
+ strPath string
+ data string
+}
+
+func generateRandomPath() (out []string) {
+ size := generator.Intn(16) + 1
+ out = make([]string, size)
+ for i := 0; i < size; i++ {
+ out[i] = Sha1(GenUUID())
+ }
+ return
+}
+func generateRandomTemplate(size int) (out []benchData) {
+ out = make([]benchData, size)
+ for i := 0; i < size; i++ {
+ out[i].path = generateRandomPath()
+ out[i].data = UUIDSha1Prefix()
+ out[i].pathItems = NewPathToItem(out[i].path)
+ out[i].strPath = out[i].pathItems.String()
+ // out[i].pathItems[len(out[i].pathItems)-1].Index = IntPointer(0)
+ }
+ return
+}
+
+func BenchmarkOrderdNavigableMapSet2(b *testing.B) {
+ nm := NewOrderedNavigableMap()
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ for _, data := range gen {
+ if _, err := nm.Set2(&FullPath{PathItems: data.pathItems, Path: data.strPath}, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+ }
+}
+
+/*
+func BenchmarkNavigableMapOld1Set(b *testing.B) {
+ nm := NewNavigableMapOld1(nil)
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ for _, data := range gen {
+ nm.Set(data.path, data.data, false)
+ }
+ }
+}
+
+func BenchmarkOrderdNavigableMapSet(b *testing.B) {
+ nm := NewOrderedNavigableMap()
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+ }
+}
+
+func BenchmarkNavigableMapSet(b *testing.B) {
+ nm := NavigableMap2{}
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+ }
+}
+func BenchmarkNavigableMapOldSet(b *testing.B) {
+ nm := NavigableMap{}
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ for _, data := range gen {
+ if _,err := nm.Set(data.path, data.data); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+ }
+}
+
+func BenchmarkOrderdNavigableMapFieldAsInterface(b *testing.B) {
+ nm := NewOrderedNavigableMap()
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.FieldAsInterface(data.path); err != nil {
+ b.Log(err)
+ } else if (*(val.(*NMSlice)))[0].Interface() != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val)
+ }
+ }
+ }
+}
+
+func BenchmarkNavigableMapFieldAsInterface(b *testing.B) {
+ nm := NavigableMap2{}
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.FieldAsInterface(data.path); err != nil {
+ b.Log(err)
+ } else if (*(val.(*NMSlice)))[0].Interface() != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val)
+ }
+ }
+ }
+}
+
+func BenchmarkNavigableMapOldFieldAsInterface(b *testing.B) {
+ nm := NavigableMap{}
+ for _, data := range gen {
+ if _,err := nm.Set(data.path, data.data); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.FieldAsInterface(data.path); err != nil {
+ b.Log(err)
+ } else if val != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val)
+ }
+ }
+ }
+}
+
+func BenchmarkNavigableMapOld1FieldAsInterface(b *testing.B) {
+ nm := NewNavigableMapOld1(nil)
+ for _, data := range gen {
+ nm.Set(data.path, data.data, true)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.FieldAsInterface(data.path); err != nil {
+ b.Log(err)
+ } else if val != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val)
+ }
+ }
+ }
+}
+
+func BenchmarkOrderdNavigableMapField(b *testing.B) {
+ nm := NewOrderedNavigableMap()
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.Field(data.pathItems); err != nil {
+ b.Log(err)
+ } else if val.Interface() != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val.Interface())
+ }
+ }
+ }
+}
+
+func BenchmarkNavigableMapField(b *testing.B) {
+ nm := NavigableMap2{}
+ for _, data := range gen {
+ if _,err := nm.Set(data.pathItems, NewNMData(data.data)); err != nil {
+ b.Log(err, data.path)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, data := range gen {
+ if val, err := nm.Field(data.pathItems); err != nil {
+ b.Log(err)
+ } else if val.Interface() != data.data {
+ b.Errorf("Expected %q ,received: %q", data.data, val.Interface())
+ }
+ }
+ }
+}
+//*/
diff --git a/utils/pathitem.go b/utils/pathitem.go
new file mode 100644
index 000000000..121ab6ade
--- /dev/null
+++ b/utils/pathitem.go
@@ -0,0 +1,149 @@
+/*
+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 (
+ "strconv"
+ "strings"
+)
+
+// stripIdxFromLastPathElm will remove the index from the last path element
+func stripIdxFromLastPathElm(path string) string {
+ lastDotIdx := strings.LastIndexByte(path, '.')
+ lastIdxStart := strings.LastIndexByte(path, '[')
+ if lastIdxStart == -1 ||
+ (lastDotIdx != -1 && lastDotIdx > lastIdxStart) {
+ return path
+ }
+ return path[:lastIdxStart]
+}
+
+// FullPath is the path to the item with all the needed fields
+type FullPath struct {
+ PathItems PathItems
+ Path string
+}
+
+// NewPathToItem returns the computed PathItems out of slice one
+func NewPathToItem(path []string) (pItms PathItems) {
+ pItms = make(PathItems, len(path))
+ for i, v := range path {
+ field, indx := GetPathIndex(v)
+ pItms[i] = PathItem{
+ Field: field,
+ Index: indx,
+ }
+ }
+ return
+}
+
+// PathItems a list of PathItem used to describe the path to an item from a NavigableMap
+type PathItems []PathItem
+
+// Clone creates a copy
+func (path PathItems) Clone() (c PathItems) {
+ if path == nil {
+ return
+ }
+ c = make(PathItems, len(path))
+ for i, v := range path {
+ c[i] = v.Clone()
+ }
+ return
+}
+
+func (path PathItems) String() (out string) {
+ for _, v := range path {
+ out += NestingSep + v.String()
+ }
+ if out == "" {
+ return
+ }
+ return out[1:]
+
+}
+
+// PathItem used by the NM interface to store the path information
+type PathItem struct {
+ Field string
+ Index *int
+}
+
+// Equal returns true if p==p2
+func (p PathItem) Equal(p2 PathItem) bool {
+ if p.Field != p2.Field {
+ return false
+ }
+ if p.Index == nil && p2.Index == nil {
+ return true
+ }
+ if p.Index != nil && p2.Index != nil {
+ return *p.Index == *p2.Index
+ }
+ return false
+}
+
+func (p PathItem) String() (out string) {
+ out = p.Field
+ if p.Index != nil {
+ out += IdxStart + strconv.Itoa(*p.Index) + IdxEnd
+ }
+ return
+}
+
+// Clone creates a copy
+func (p PathItem) Clone() (c PathItem) {
+ // if p == nil {
+ // return
+ // }
+ c.Field = p.Field
+ if p.Index != nil {
+ c.Index = IntPointer(*p.Index)
+ }
+ return
+}
+
+// GetPathIndex returns the path and index if index present
+// path[index]=>path,index
+// path=>path,nil
+func GetPathIndex(spath string) (opath string, idx *int) {
+ idxStart := strings.Index(spath, IdxStart)
+ if idxStart == -1 || !strings.HasSuffix(spath, IdxEnd) {
+ return spath, nil
+ }
+ slctr := spath[idxStart+1 : len(spath)-1]
+ opath = spath[:idxStart]
+ // if strings.HasPrefix(slctr, DynamicDataPrefix) {
+ // return
+ // }
+ idxVal, err := strconv.Atoi(slctr)
+ if err != nil {
+ return spath, nil
+ }
+ return opath, &idxVal
+}
+
+func GetPathWithoutIndex(spath string) (opath string) {
+ idxStart := strings.LastIndex(spath, IdxStart)
+ if idxStart == -1 || !strings.HasSuffix(spath, IdxEnd) {
+ return spath
+ }
+ opath = spath[:idxStart]
+ return
+}
diff --git a/utils/pathitem_test.go b/utils/pathitem_test.go
new file mode 100644
index 000000000..a44b7bdec
--- /dev/null
+++ b/utils/pathitem_test.go
@@ -0,0 +1,147 @@
+/*
+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 (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestStripIdxFromLastPathElm(t *testing.T) {
+ str := ""
+ if strp := stripIdxFromLastPathElm(str); strp != "" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath[0]"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath.mypath2[0]"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath.mypath2" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath.mypath2"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath.mypath2" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath[1].mypath2[0]"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath[1].mypath2" {
+ t.Errorf("received: <%s>", strp)
+ }
+ str = "mypath[1].mypath2"
+ if strp := stripIdxFromLastPathElm(str); strp != "mypath[1].mypath2" {
+ t.Errorf("received: <%s>", strp)
+ }
+}
+
+func TestNewPathToItem(t *testing.T) {
+ pathSlice := strings.Split("*req.Field1[0].Account", NestingSep)
+ expected := PathItems{{Field: MetaReq}, {Field: "Field1", Index: IntPointer(0)}, {Field: Account}}
+ if rply := NewPathToItem(pathSlice); !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+ pathSlice = []string{}
+ expected = PathItems{}
+ if rply := NewPathToItem(pathSlice); !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
+
+func TestPathItemString(t *testing.T) {
+ path := PathItem{Field: MetaReq}
+ expected := MetaReq
+ if rply := path.String(); expected != rply {
+ t.Errorf("Expected: %q, received: %q", expected, rply)
+ }
+ path = PathItem{Field: MetaReq, Index: IntPointer(10)}
+ expected = MetaReq + "[10]"
+ if rply := path.String(); expected != rply {
+ t.Errorf("Expected: %q, received: %q", expected, rply)
+ }
+}
+
+func TestPathItemEqual(t *testing.T) {
+ path := PathItem{Field: MetaReq}
+ p1 := PathItem{Field: MetaReq}
+ if !path.Equal(p1) {
+ t.Errorf("Expected %s to be equal to %s", ToJSON(path), ToJSON(p1))
+ }
+ p1 = PathItem{Field: MetaRep}
+ if path.Equal(p1) {
+ t.Errorf("Expected %s to not be equal to %s", ToJSON(path), ToJSON(p1))
+ }
+ p1 = PathItem{Field: MetaReq, Index: IntPointer(0)}
+ if path.Equal(p1) {
+ t.Errorf("Expected %s to not be equal to %s", ToJSON(path), ToJSON(p1))
+ }
+ path = PathItem{Field: MetaReq, Index: IntPointer(0)}
+ if !path.Equal(p1) {
+ t.Errorf("Expected %s to be equal to %s", ToJSON(path), ToJSON(p1))
+ }
+}
+
+func TestPathItemClone(t *testing.T) {
+ path := PathItem{Field: MetaReq, Index: IntPointer(0)}
+ expected := PathItem{Field: MetaReq, Index: IntPointer(0)}
+ rply := path.Clone()
+ *path.Index = 1
+ if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+
+ var path2 PathItem
+ expected = PathItem{}
+ rply = path2.Clone()
+ if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
+
+func TestPathItemsString(t *testing.T) {
+ expected := "*req.Field1[0].Account"
+ path := NewPathToItem(strings.Split(expected, NestingSep))
+ if rply := path.String(); expected != rply {
+ t.Errorf("Expected: %q, received: %q", expected, rply)
+ }
+ expected = ""
+ path = nil
+ if rply := path.String(); expected != rply {
+ t.Errorf("Expected: %q, received: %q", expected, rply)
+ }
+}
+
+func TestPathItemsClone(t *testing.T) {
+ path := NewPathToItem(strings.Split("*req.Field1[0].Account", NestingSep))
+ expected := NewPathToItem(strings.Split("*req.Field1[0].Account", NestingSep))
+ rply := path.Clone()
+ path[0] = PathItem{}
+ if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+ expected = nil
+ path = nil
+ rply = path.Clone()
+ if !reflect.DeepEqual(expected, rply) {
+ t.Errorf("Expected: %s, received: %s", ToJSON(expected), ToJSON(rply))
+ }
+}
diff --git a/utils/pathitemlist.go b/utils/pathitemlist.go
new file mode 100644
index 000000000..21e4c8bcc
--- /dev/null
+++ b/utils/pathitemlist.go
@@ -0,0 +1,248 @@
+/*
+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
+
+// PathItemElement is an element of a linked list.
+// Inspired by Go's double linked list
+type PathItemElement struct {
+ // Next and previous pointers in the doubly-linked list of elements.
+ // To simplify the implementation, internally a list l is implemented
+ // as a ring, such that &l.root is both the next element of the last
+ // list element (l.Back()) and the previous element of the first list
+ // element (l.Front()).
+ next, prev *PathItemElement
+
+ // The list to which this element belongs.
+ list *PathItemList
+
+ // The value stored with this element.
+ Value PathItems
+}
+
+// Next returns the next list element or nil.
+func (e *PathItemElement) Next() *PathItemElement {
+ if p := e.next; e.list != nil && p != &e.list.root {
+ return p
+ }
+ return nil
+}
+
+// Prev returns the previous list element or nil.
+func (e *PathItemElement) Prev() *PathItemElement {
+ if p := e.prev; e.list != nil && p != &e.list.root {
+ return p
+ }
+ return nil
+}
+
+// PathItemList represents a doubly linked list.
+// The zero value for PathItemList is an empty list ready to use.
+type PathItemList struct {
+ root PathItemElement // sentinel list element, only &root, root.prev, and root.next are used
+ len int // current list length excluding (this) sentinel element
+}
+
+// Init initializes or clears list l.
+func (l *PathItemList) Init() *PathItemList {
+ l.root.next = &l.root
+ l.root.prev = &l.root
+ l.len = 0
+ return l
+}
+
+// NewPathItemList returns an initialized list.
+func NewPathItemList() *PathItemList { return new(PathItemList).Init() }
+
+// Len returns the number of elements of list l.
+// The complexity is O(1).
+func (l *PathItemList) Len() int { return l.len }
+
+// Front returns the first element of list l or nil if the list is empty.
+func (l *PathItemList) Front() *PathItemElement {
+ if l.len == 0 {
+ return nil
+ }
+ return l.root.next
+}
+
+// Back returns the last element of list l or nil if the list is empty.
+func (l *PathItemList) Back() *PathItemElement {
+ if l.len == 0 {
+ return nil
+ }
+ return l.root.prev
+}
+
+// lazyInit lazily initializes a zero PathItemList value.
+func (l *PathItemList) lazyInit() {
+ if l.root.next == nil {
+ l.Init()
+ }
+}
+
+// insert inserts e after at, increments l.len, and returns e.
+func (l *PathItemList) insert(e, at *PathItemElement) *PathItemElement {
+ n := at.next
+ at.next = e
+ e.prev = at
+ e.next = n
+ n.prev = e
+ e.list = l
+ l.len++
+ return e
+}
+
+// insertValue is a convenience wrapper for insert(&PathItemElement{Value: v}, at).
+func (l *PathItemList) insertValue(v PathItems, at *PathItemElement) *PathItemElement {
+ return l.insert(&PathItemElement{Value: v}, at)
+}
+
+// remove removes e from its list, decrements l.len, and returns e.
+func (l *PathItemList) remove(e *PathItemElement) *PathItemElement {
+ e.prev.next = e.next
+ e.next.prev = e.prev
+ e.next = nil // avoid memory leaks
+ e.prev = nil // avoid memory leaks
+ e.list = nil
+ l.len--
+ return e
+}
+
+// move moves e to next to at and returns e.
+func (l *PathItemList) move(e, at *PathItemElement) *PathItemElement {
+ if e == at {
+ return e
+ }
+ e.prev.next = e.next
+ e.next.prev = e.prev
+
+ n := at.next
+ at.next = e
+ e.prev = at
+ e.next = n
+ n.prev = e
+
+ return e
+}
+
+// Remove removes e from l if e is an element of list l.
+// It returns the element value e.Value.
+// The element must not be nil.
+func (l *PathItemList) Remove(e *PathItemElement) PathItems {
+ if e.list == l {
+ // if e.list == l, l must have been initialized when e was inserted
+ // in l or l == nil (e is a zero PathItemElement) and l.remove will crash
+ l.remove(e)
+ }
+ return e.Value
+}
+
+// PushFront inserts a new element e with value v at the front of list l and returns e.
+func (l *PathItemList) PushFront(v PathItems) *PathItemElement {
+ l.lazyInit()
+ return l.insertValue(v, &l.root)
+}
+
+// PushBack inserts a new element e with value v at the back of list l and returns e.
+func (l *PathItemList) PushBack(v PathItems) *PathItemElement {
+ l.lazyInit()
+ return l.insertValue(v, l.root.prev)
+}
+
+// InsertBefore inserts a new element e with value v immediately before mark and returns e.
+// If mark is not an element of l, the list is not modified.
+// The mark must not be nil.
+func (l *PathItemList) InsertBefore(v PathItems, mark *PathItemElement) *PathItemElement {
+ if mark.list != l {
+ return nil
+ }
+ // see comment in PathItemList.Remove about initialization of l
+ return l.insertValue(v, mark.prev)
+}
+
+// InsertAfter inserts a new element e with value v immediately after mark and returns e.
+// If mark is not an element of l, the list is not modified.
+// The mark must not be nil.
+func (l *PathItemList) InsertAfter(v PathItems, mark *PathItemElement) *PathItemElement {
+ if mark.list != l {
+ return nil
+ }
+ // see comment in PathItemList.Remove about initialization of l
+ return l.insertValue(v, mark)
+}
+
+// MoveToFront moves element e to the front of list l.
+// If e is not an element of l, the list is not modified.
+// The element must not be nil.
+func (l *PathItemList) MoveToFront(e *PathItemElement) {
+ if e.list != l || l.root.next == e {
+ return
+ }
+ // see comment in PathItemList.Remove about initialization of l
+ l.move(e, &l.root)
+}
+
+// MoveToBack moves element e to the back of list l.
+// If e is not an element of l, the list is not modified.
+// The element must not be nil.
+func (l *PathItemList) MoveToBack(e *PathItemElement) {
+ if e.list != l || l.root.prev == e {
+ return
+ }
+ // see comment in PathItemList.Remove about initialization of l
+ l.move(e, l.root.prev)
+}
+
+// MoveBefore moves element e to its new position before mark.
+// If e or mark is not an element of l, or e == mark, the list is not modified.
+// The element and mark must not be nil.
+func (l *PathItemList) MoveBefore(e, mark *PathItemElement) {
+ if e.list != l || e == mark || mark.list != l {
+ return
+ }
+ l.move(e, mark.prev)
+}
+
+// MoveAfter moves element e to its new position after mark.
+// If e or mark is not an element of l, or e == mark, the list is not modified.
+// The element and mark must not be nil.
+func (l *PathItemList) MoveAfter(e, mark *PathItemElement) {
+ if e.list != l || e == mark || mark.list != l {
+ return
+ }
+ l.move(e, mark)
+}
+
+// PushBackList inserts a copy of an other list at the back of list l.
+// The lists l and other may be the same. They must not be nil.
+func (l *PathItemList) PushBackList(other *PathItemList) {
+ l.lazyInit()
+ for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
+ l.insertValue(e.Value, l.root.prev)
+ }
+}
+
+// PushFrontList inserts a copy of an other list at the front of list l.
+// The lists l and other may be the same. They must not be nil.
+func (l *PathItemList) PushFrontList(other *PathItemList) {
+ l.lazyInit()
+ for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
+ l.insertValue(e.Value, &l.root)
+ }
+}
diff --git a/utils/reflect.go b/utils/reflect.go
index db2dd8e07..17faa57ba 100644
--- a/utils/reflect.go
+++ b/utils/reflect.go
@@ -267,6 +267,8 @@ func IfaceAsString(fld interface{}) (out string) {
return value.String()
case string:
return value
+ case NMInterface:
+ return value.String()
default: // Maybe we are lucky and the value converts to string
return ToJSON(fld)
}
diff --git a/utils/researchreplace_test.go b/utils/researchreplace_test.go
index 605d72684..830033076 100644
--- a/utils/researchreplace_test.go
+++ b/utils/researchreplace_test.go
@@ -22,6 +22,15 @@ import (
"testing"
)
+func TestProcessReSearchReplaceNil(t *testing.T) {
+ rsr := &ReSearchReplace{SearchRegexp: nil, ReplaceTemplate: "0$1@$2"}
+ source := ""
+ expectOut := ""
+ if outStr := rsr.Process(source); outStr != expectOut {
+ t.Error("Unexpected output from SearchReplace: ", outStr)
+ }
+}
+
func TestProcessReSearchReplace(t *testing.T) {
rsr := &ReSearchReplace{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@(\d*\.\d*\.\d*\.\d*)`), ReplaceTemplate: "0$1@$2"}
source := ""
diff --git a/utils/set.go b/utils/set.go
index 470b4f9bb..770c1752e 100644
--- a/utils/set.go
+++ b/utils/set.go
@@ -19,67 +19,57 @@ along with this program. If not, see
package utils
// NewStringSet returns a new StringSet
-func NewStringSet(dataSlice []string) (s *StringSet) {
- s = &StringSet{data: make(map[string]struct{})}
+func NewStringSet(dataSlice []string) (s StringSet) {
+ s = make(StringSet)
s.AddSlice(dataSlice)
return s
}
// StringSet will manage data within a set
-type StringSet struct {
- data map[string]struct{}
-}
+type StringSet map[string]struct{}
// Add adds a key in set
-func (s *StringSet) Add(val string) {
- s.data[val] = struct{}{}
+func (s StringSet) Add(val string) {
+ s[val] = struct{}{}
}
// Remove removes a key from set
-func (s *StringSet) Remove(val string) {
- delete(s.data, val)
+func (s StringSet) Remove(val string) {
+ delete(s, val)
}
// Has returns if the key is in set
-func (s *StringSet) Has(val string) bool {
- _, has := s.data[val]
+func (s StringSet) Has(val string) bool {
+ _, has := s[val]
return has
}
// AddSlice adds all the element of a slice
-func (s *StringSet) AddSlice(dataSlice []string) {
+func (s StringSet) AddSlice(dataSlice []string) {
for _, val := range dataSlice {
s.Add(val)
}
}
// AsSlice returns the keys as string slice
-func (s *StringSet) AsSlice() []string {
- result := make([]string, len(s.data))
+func (s StringSet) AsSlice() []string {
+ result := make([]string, len(s))
i := 0
- for k := range s.data {
+ for k := range s {
result[i] = k
i++
}
return result
}
-// Data exports the internal map, so we can benefit for example of key iteration
-func (s *StringSet) Data() map[string]struct{} {
- return s.data
-}
-
// Size returns the size of the set
-func (s *StringSet) Size() int {
- if s == nil || s.data == nil {
- return 0
- }
- return len(s.data)
+func (s StringSet) Size() int {
+ return len(s)
}
// Intersect removes all key s2 do not have
-func (s *StringSet) Intersect(s2 *StringSet) {
- for k := range s.data {
+func (s StringSet) Intersect(s2 StringSet) {
+ for k := range s {
if !s2.Has(k) {
s.Remove(k)
}
diff --git a/utils/set_test.go b/utils/set_test.go
new file mode 100644
index 000000000..eaebc30d9
--- /dev/null
+++ b/utils/set_test.go
@@ -0,0 +1,161 @@
+/*
+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 (
+ "reflect"
+ "sort"
+ "testing"
+)
+
+func TestNewStringSet(t *testing.T) {
+ input := []string{}
+ exp := make(StringSet)
+ if rcv := NewStringSet(input); !reflect.DeepEqual(rcv, exp) {
+ t.Errorf("Expected: %+v, received: %+v", exp, rcv)
+ }
+ input = []string{"test"}
+ exp.AddSlice(input)
+ if rcv := NewStringSet(input); !reflect.DeepEqual(rcv, exp) {
+ t.Errorf("Expected: %+v, received: %+v", exp, rcv)
+ }
+ input = []string{"test1", "test2", "test3"}
+ exp = make(StringSet)
+ exp.AddSlice(input)
+ if rcv := NewStringSet(input); !reflect.DeepEqual(rcv, exp) {
+ t.Errorf("Expected: %+v, received: %+v", exp, rcv)
+ }
+}
+
+func TestAdd(t *testing.T) {
+ s := make(StringSet)
+ eOut := StringSet{
+ "test": struct{}{},
+ }
+ if reflect.DeepEqual(eOut, s) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s)
+ }
+ s.Add("test")
+ if !reflect.DeepEqual(eOut, s) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s)
+ }
+}
+
+func TestRemove(t *testing.T) {
+ eOut := make(StringSet)
+ s := StringSet{
+ "test": struct{}{},
+ }
+ if reflect.DeepEqual(eOut, s) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s)
+ }
+ s.Remove("test")
+ if !reflect.DeepEqual(eOut, s) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s)
+ }
+}
+
+func TestHas(t *testing.T) {
+ s := StringSet{}
+ if s.Has("test") {
+ t.Error("Expecting: false, received: true")
+ }
+ s = StringSet{
+ "test": struct{}{},
+ }
+ if !s.Has("test") {
+ t.Error("Expecting: true, received: false")
+ }
+}
+
+func TestAddSlice(t *testing.T) {
+ s := StringSet{
+ "test": struct{}{}}
+ eOut := StringSet{
+ "test": struct{}{},
+ "test1": struct{}{},
+ "test2": struct{}{}}
+ s.AddSlice([]string{"test1", "test2"})
+ if !reflect.DeepEqual(eOut, s) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s)
+ }
+}
+
+func TestAsSlice(t *testing.T) {
+ s := StringSet{}
+ eOut := make([]string, 0)
+ if rcv := s.AsSlice(); !reflect.DeepEqual(eOut, rcv) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, rcv)
+ }
+ s = StringSet{
+ "test": struct{}{},
+ "test1": struct{}{},
+ "test2": struct{}{}}
+ eOut = []string{"test", "test1", "test2"}
+ rcv := s.AsSlice()
+ sort.Strings(rcv)
+ if !reflect.DeepEqual(eOut, rcv) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, rcv)
+ }
+}
+
+func TestSize(t *testing.T) {
+ s := StringSet{}
+ if rcv := s.Size(); rcv != 0 {
+ t.Errorf("Expecting: 0, received %+v", rcv)
+ }
+ s = StringSet{
+ "test0": struct{}{},
+ "test1": struct{}{},
+ "test2": struct{}{}}
+ if rcv := s.Size(); rcv != 3 {
+ t.Errorf("Expecting: 3, received %+v", rcv)
+ }
+}
+
+func TestIntersect(t *testing.T) {
+ s1 := StringSet{
+ "test0": struct{}{},
+ "test1": struct{}{},
+ "test2": struct{}{}}
+ s2 := StringSet{
+ "test0": struct{}{},
+ "test2": struct{}{},
+ "test3": struct{}{}}
+ eOut := StringSet{
+ "test0": struct{}{},
+ "test2": struct{}{}}
+ s1.Intersect(s2)
+ if !reflect.DeepEqual(eOut, s1) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s1)
+ }
+ s1 = StringSet{
+ "test0": struct{}{},
+ "test1": struct{}{},
+ "test2": struct{}{}}
+ s2 = StringSet{
+ "test3": struct{}{},
+ "test4": struct{}{},
+ "test5": struct{}{}}
+ s1.Intersect(s2)
+ eOut = make(StringSet)
+ if !reflect.DeepEqual(eOut, s1) {
+ t.Errorf("Expecting: %+v, received: %+v", eOut, s1)
+ }
+}