From 3b9b2895e0117d19f1dbceb203d9e99a05560f41 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 May 2020 14:11:48 +0300 Subject: [PATCH] Added OrderedNavigableMap implementation --- agents/kamevent.go | 2 +- config/config.go | 2 +- config/configsanity.go | 2 +- config/sessionscfg.go | 4 +- engine/cdrs.go | 2 +- engine/datamanager.go | 2 +- engine/filters.go | 6 +- engine/mapevent.go | 2 +- engine/safevent.go | 2 +- engine/storage_internal_stordb.go | 4 +- sessions/libsessions.go | 4 +- sessions/session.go | 4 +- utils/coreutils.go | 20 - utils/coreutils_test.go | 2 +- utils/dataprovider.go | 123 +++++ utils/dataprovider_test.go | 190 +++++++ utils/errors.go | 2 + utils/navigablemap.go | 391 ++++++------- utils/navigablemap_test.go | 440 +++++++++++++++ utils/nmdata.go | 70 +++ utils/nmdata_test.go | 109 ++++ utils/nmslice.go | 140 +++++ utils/nmslice_test.go | 206 +++++++ utils/orderednavigablemap.go | 184 +++++++ utils/orderednavigablemap_test.go | 885 ++++++++++++++++++++++++++++++ utils/pathitem.go | 149 +++++ utils/pathitem_test.go | 147 +++++ utils/pathitemlist.go | 248 +++++++++ utils/reflect.go | 2 + utils/researchreplace_test.go | 9 + utils/set.go | 44 +- utils/set_test.go | 78 ++- 32 files changed, 3131 insertions(+), 344 deletions(-) create mode 100644 utils/dataprovider.go create mode 100644 utils/dataprovider_test.go create mode 100644 utils/navigablemap_test.go create mode 100644 utils/nmdata.go create mode 100644 utils/nmdata_test.go create mode 100644 utils/nmslice.go create mode 100644 utils/nmslice_test.go create mode 100644 utils/orderednavigablemap.go create mode 100644 utils/orderednavigablemap_test.go create mode 100644 utils/pathitem.go create mode 100644 utils/pathitem_test.go create mode 100644 utils/pathitemlist.go diff --git a/agents/kamevent.go b/agents/kamevent.go index 85e087e9f..8f0c40b99 100644 --- a/agents/kamevent.go +++ b/agents/kamevent.go @@ -450,7 +450,7 @@ func (kdr *KamDlgReply) String() string { // GetOptions returns the posible options func (kev KamEvent) GetOptions() (mp map[string]interface{}) { mp = make(map[string]interface{}) - for k := range utils.CGROptionsSet.Data() { + for k := range utils.CGROptionsSet { if val, has := kev[k]; has { mp[k] = val } diff --git a/config/config.go b/config/config.go index e11ad1898..b179e800a 100755 --- a/config/config.go +++ b/config/config.go @@ -429,7 +429,7 @@ func (cfg *CGRConfig) loadDataDBCfg(jsnCfg *CgrJsonCfg) (err error) { // so we enforce it here if cfg.dataDbCfg.DataDbType == utils.INTERNAL { // overwrite only DataDBPartitions and leave other unmodified ( e.g. *diameter_messages, *closed_sessions, etc... ) - for key := range utils.CacheDataDBPartitions.Data() { + for key := range utils.CacheDataDBPartitions { if _, has := cfg.cacheCfg.Partitions[key]; has { cfg.cacheCfg.Partitions[key] = &CacheParamCfg{Limit: 0, TTL: time.Duration(0), StaticTTL: false, Precache: false} diff --git a/config/configsanity.go b/config/configsanity.go index f3e3ffcf3..2a99a2f50 100644 --- a/config/configsanity.go +++ b/config/configsanity.go @@ -199,7 +199,7 @@ func (cfg *CGRConfig) checkConfigSanity() error { return fmt.Errorf("<%s> %s needs to be != 0, received: %d", utils.CacheS, utils.CacheClosedSessions, cfg.cacheCfg.Partitions[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/sessionscfg.go b/config/sessionscfg.go index ea4fc69f6..6ec5f2d91 100644 --- a/config/sessionscfg.go +++ b/config/sessionscfg.go @@ -97,7 +97,7 @@ type SessionSCfg struct { ClientProtocol float64 ChannelSyncInterval time.Duration TerminateAttempts int - AlterableFields *utils.StringSet + AlterableFields utils.StringSet MinDurLowBalance time.Duration SchedulerConns []string STIRCfg *STIRcfg @@ -681,7 +681,7 @@ func (aCfg *AsteriskAgentCfg) AsMapInterface() map[string]interface{} { // STIRcfg the confuguration structure for STIR type STIRcfg struct { - AllowedAttest *utils.StringSet + AllowedAttest utils.StringSet PayloadMaxduration time.Duration DefaultAttest string PublicKeyPath string diff --git a/engine/cdrs.go b/engine/cdrs.go index b3f731d1b..9d11da5ef 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -483,7 +483,7 @@ func (cdrS *CDRServer) processEvent(ev *utils.CGREventWithOpts, } } } - procFlgs := make([]*utils.StringSet, len(cgrEvs)) // will save the flags for the reply here + procFlgs := make([]utils.StringSet, len(cgrEvs)) // will save the flags for the reply here for i := range cgrEvs { procFlgs[i] = utils.NewStringSet(nil) } diff --git a/engine/datamanager.go b/engine/datamanager.go index 35fd239f9..49284d62c 100644 --- a/engine/datamanager.go +++ b/engine/datamanager.go @@ -2415,7 +2415,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 cd4533ac2..1bc870d6b 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -199,16 +199,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 24f75afeb..a4faca85c 100644 --- a/engine/storage_internal_stordb.go +++ b/engine/storage_internal_stordb.go @@ -1096,7 +1096,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 { @@ -1173,7 +1173,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/libsessions.go b/sessions/libsessions.go index 1460473fa..a002ba9f5 100644 --- a/sessions/libsessions.go +++ b/sessions/libsessions.go @@ -163,7 +163,7 @@ func (pi *ProcessedStirIdentity) VerifySignature(timeoutVal time.Duration) (err // VerifyPayload returns if the payload is corectly populated func (pi *ProcessedStirIdentity) VerifyPayload(originatorTn, originatorURI, destinationTn, destinationURI string, - hdrMaxDur time.Duration, attest *utils.StringSet) (err error) { + hdrMaxDur time.Duration, attest utils.StringSet) (err error) { if !attest.Has(utils.META_ANY) && !attest.Has(pi.Payload.ATTest) { return errors.New("wrong attest level") } @@ -221,7 +221,7 @@ func NewSTIRIdentity(header *utils.PASSporTHeader, payload *utils.PASSporTPayloa // AuthStirShaken autentificates the given identity using STIR/SHAKEN func AuthStirShaken(identity, originatorTn, originatorURI, destinationTn, destinationURI string, - attest *utils.StringSet, hdrMaxDur time.Duration) (err error) { + attest utils.StringSet, hdrMaxDur time.Duration) (err error) { var pi *ProcessedStirIdentity if pi, err = NewProcessedIdentity(identity); err != nil { return diff --git a/sessions/session.go b/sessions/session.go index f54b2ca3d..1fabb907f 100644 --- a/sessions/session.go +++ b/sessions/session.go @@ -308,7 +308,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 } @@ -323,7 +323,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 7787bb954..980d23f8d 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -893,26 +893,6 @@ 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 -} - type GetFilterIndexesArgWithArgDispatcher struct { *GetFilterIndexesArg TenantArg diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go index f4d0d1c7a..3a9b1dd83 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 4b7dee197..1947c10d8 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 index 76dd55d80..083a35860 100644 --- a/utils/navigablemap.go +++ b/utils/navigablemap.go @@ -19,254 +19,181 @@ along with this program. If not, see package utils import ( - "fmt" - "strings" + "net" ) -// DataStorage is the new DataProvider -type DataStorage interface { - String() string // printable version of data - Get(fldPath []string) (interface{}, error) - GetString(fldPath []string) (string, error) - Set(fldPath []string, val interface{}) error - Remove(fldPath []string) error - GetKeys(nesteed bool) []string - // RemoteHost() net.Addr +// 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 + "}" } -// MapStorage is the basic DataStorage -type MapStorage map[string]interface{} - -// String returns the map as json string -func (ms *MapStorage) String() string { return ToJSON(ms) } - -// Get returns the value from the path -func (ms *MapStorage) Get(fldPath []string) (val interface{}, err error) { - if len(fldPath) == 0 { - err = ErrNotFound - return - } - var has bool - if val, has = (*ms)[fldPath[0]]; !has { - err = ErrNotFound - return - } - if len(fldPath) == 1 { - return - } - ds, ok := val.(DataStorage) - if !ok { - err = fmt.Errorf("Wrong type") - return - } - return ds.Get(fldPath[1:]) +// Interface returns itself +func (nm NavigableMap2) Interface() interface{} { + return nm } -// GetString returns thevalue from path as string -func (ms *MapStorage) GetString(fldPath []string) (str string, err error) { - var val interface{} - if val, err = ms.Get(fldPath); err != nil { - return +// 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 } - return IfaceAsString(val), nil -} - -// Set sets the value at the given path -func (ms *MapStorage) Set(fldPath []string, val interface{}) (err error) { - if len(fldPath) == 0 { - return fmt.Errorf("Wrong path") - } - if len(fldPath) == 1 { - (*ms)[fldPath[0]] = val - return - } - nMap := &MapStorage{} - (*ms)[fldPath[0]] = nMap - return nMap.Set(fldPath[1:], val) -} - -// GetKeys returns all the keys from map -func (ms *MapStorage) GetKeys(nesteed bool) (keys []string) { - for k, v := range *ms { - keys = append(keys, k) - if !nesteed { - continue - } - ds, ok := v.(DataStorage) - if !ok { - continue - } - for _, dsKey := range ds.GetKeys(nesteed) { - keys = append(keys, k+NestingSep+dsKey) - } - } - return -} - -// Remove removes the item at path -func (ms *MapStorage) Remove(fldPath []string) (err error) { - if len(fldPath) == 0 { - return fmt.Errorf("Wrong path") - } - var val interface{} - var has bool - if val, has = (*ms)[fldPath[0]]; !has { - return // ignore (already removed) - } - if len(fldPath) == 1 { - delete(*ms, fldPath[0]) - return - } - ds, ok := val.(DataStorage) - if !ok { - err = fmt.Errorf("Wrong type") - return - } - return ds.Remove(fldPath[1:]) -} - -// NavigableMap is a DataStorage -type NavigableMap map[string]DataStorage - -// String returns the map as json string -func (nm *NavigableMap) String() string { return ToJSON(nm) } - -// Get returns the value from the path -func (nm *NavigableMap) Get(fldPath []string) (val interface{}, err error) { - if len(fldPath) == 0 { - err = ErrNotFound - return - } - ds, has := (*nm)[fldPath[0]] + el, has := nm[path[0].Field] if !has { - err = ErrNotFound - return + return nil, ErrNotFound } - if len(fldPath) == 1 { - val = ds - return + 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) } - return ds.Get(fldPath[1:]) } -// GetString returns thevalue from path as string -func (nm *NavigableMap) GetString(fldPath []string) (str string, err error) { +// 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{} - if val, err = nm.Get(fldPath); err != nil { + val, err = nm.FieldAsInterface(fldPath) + if err != nil { return } return IfaceAsString(val), nil } -// Set sets the value at the given path -func (nm *NavigableMap) Set(fldPath []string, val interface{}) (err error) { - if len(fldPath) == 0 { - return fmt.Errorf("Wrong path") - } - if len(fldPath) == 1 { - ds, ok := val.(DataStorage) - if !ok { - return fmt.Errorf("Wrong type") - } - (*nm)[fldPath[0]] = ds - return - } - if _, has := (*nm)[fldPath[0]]; !has { - (*nm)[fldPath[0]] = &MapStorage{} - } - return (*nm)[fldPath[0]].Set(fldPath[1:], val) -} - -// GetKeys returns all the keys from map -func (nm *NavigableMap) GetKeys(nesteed bool) (keys []string) { - for k, v := range *nm { - keys = append(keys, k) - if !nesteed { - continue - } - for _, dsKey := range v.GetKeys(nesteed) { - keys = append(keys, k+NestingSep+dsKey) - } - } - return -} - -// Remove removes the item at path -func (nm *NavigableMap) Remove(fldPath []string) (err error) { - if len(fldPath) == 0 { - return fmt.Errorf("Wrong path") - } - var val DataStorage - var has bool - if val, has = (*nm)[fldPath[0]]; !has { - return // ignore (already removed) - } - if len(fldPath) == 1 { - delete(*nm, fldPath[0]) - return - } - return val.Remove(fldPath[1:]) -} - -// NewOrderedNavigableMap initializates a structure of OrderedNavigableMap with a NavigableMap -func NewOrderedNavigableMap(nm *NavigableMap) *OrderedNavigableMap { - if nm == nil { - return &OrderedNavigableMap{ - nm: &NavigableMap{}, - order: [][]string{}, - } - } - keys := nm.GetKeys(true) - order := make([][]string, len(keys)) - for i, k := range keys { - order[i] = strings.Split(k, NestingSep) - } - return &OrderedNavigableMap{ - nm: nm, - order: order, - } -} - -// OrderedNavigableMap is the same as NavigableMap but keeps the order of fields -type OrderedNavigableMap struct { - nm *NavigableMap - order [][]string -} - -// String returns the map as json string -func (onm *OrderedNavigableMap) String() string { return ToJSON(onm.nm) } - -// Get returns the value from the path -func (onm *OrderedNavigableMap) Get(fldPath []string) (val interface{}, err error) { - return onm.nm.Get(fldPath) -} - -// GetString returns thevalue from path as string -func (onm *OrderedNavigableMap) GetString(fldPath []string) (str string, err error) { - return onm.nm.GetString(fldPath) -} - -// Set sets the value at the given path -func (onm *OrderedNavigableMap) Set(fldPath []string, val interface{}) (err error) { - if err = onm.nm.Set(fldPath, val); err == nil { - onm.order = append(onm.order, fldPath) - } - return -} - -// GetKeys returns all the keys from map -func (onm *OrderedNavigableMap) GetKeys(nesteed bool) (keys []string) { - keys = make([]string, len(onm.order)) - for i, k := range onm.order { - keys[i] = strings.Join(k, NestingSep) - } - return -} - -// Remove removes the item at path -func (onm *OrderedNavigableMap) Remove(fldPath []string) (err error) { - if len(fldPath) == 0 { - return fmt.Errorf("Wrong path") - } - return onm.nm.Remove(fldPath) +// 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 72e891170..08e4d3394 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 index ed5ffed66..eaebc30d9 100644 --- a/utils/set_test.go +++ b/utils/set_test.go @@ -26,7 +26,7 @@ import ( func TestNewStringSet(t *testing.T) { input := []string{} - exp := &StringSet{data: make(map[string]struct{})} + exp := make(StringSet) if rcv := NewStringSet(input); !reflect.DeepEqual(rcv, exp) { t.Errorf("Expected: %+v, received: %+v", exp, rcv) } @@ -36,7 +36,7 @@ func TestNewStringSet(t *testing.T) { t.Errorf("Expected: %+v, received: %+v", exp, rcv) } input = []string{"test1", "test2", "test3"} - exp = &StringSet{data: make(map[string]struct{})} + exp = make(StringSet) exp.AddSlice(input) if rcv := NewStringSet(input); !reflect.DeepEqual(rcv, exp) { t.Errorf("Expected: %+v, received: %+v", exp, rcv) @@ -44,10 +44,10 @@ func TestNewStringSet(t *testing.T) { } func TestAdd(t *testing.T) { - s := &StringSet{data: map[string]struct{}{}} - eOut := &StringSet{data: map[string]struct{}{ + s := make(StringSet) + eOut := StringSet{ "test": struct{}{}, - }} + } if reflect.DeepEqual(eOut, s) { t.Errorf("Expecting: %+v, received: %+v", eOut, s) } @@ -58,10 +58,10 @@ func TestAdd(t *testing.T) { } func TestRemove(t *testing.T) { - eOut := &StringSet{data: map[string]struct{}{}} - s := &StringSet{data: map[string]struct{}{ + eOut := make(StringSet) + s := StringSet{ "test": struct{}{}, - }} + } if reflect.DeepEqual(eOut, s) { t.Errorf("Expecting: %+v, received: %+v", eOut, s) } @@ -72,25 +72,25 @@ func TestRemove(t *testing.T) { } func TestHas(t *testing.T) { - s := &StringSet{} + s := StringSet{} if s.Has("test") { t.Error("Expecting: false, received: true") } - s = &StringSet{data: map[string]struct{}{ + s = StringSet{ "test": struct{}{}, - }} + } if !s.Has("test") { t.Error("Expecting: true, received: false") } } func TestAddSlice(t *testing.T) { - s := &StringSet{data: map[string]struct{}{ - "test": struct{}{}}} - eOut := &StringSet{data: map[string]struct{}{ + s := StringSet{ + "test": struct{}{}} + eOut := StringSet{ "test": struct{}{}, "test1": struct{}{}, - "test2": struct{}{}}} + "test2": struct{}{}} s.AddSlice([]string{"test1", "test2"}) if !reflect.DeepEqual(eOut, s) { t.Errorf("Expecting: %+v, received: %+v", eOut, s) @@ -98,15 +98,15 @@ func TestAddSlice(t *testing.T) { } func TestAsSlice(t *testing.T) { - s := &StringSet{} + s := StringSet{} eOut := make([]string, 0) if rcv := s.AsSlice(); !reflect.DeepEqual(eOut, rcv) { t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) } - s = &StringSet{data: map[string]struct{}{ + s = StringSet{ "test": struct{}{}, "test1": struct{}{}, - "test2": struct{}{}}} + "test2": struct{}{}} eOut = []string{"test", "test1", "test2"} rcv := s.AsSlice() sort.Strings(rcv) @@ -115,60 +115,46 @@ func TestAsSlice(t *testing.T) { } } -func TestData(t *testing.T) { - s := &StringSet{data: map[string]struct{}{ - "test": struct{}{}, - "test1": struct{}{}, - "test2": struct{}{}}} - eOut := map[string]struct{}{ - "test": struct{}{}, - "test1": struct{}{}, - "test2": struct{}{}} - if rcv := s.Data(); !reflect.DeepEqual(eOut, rcv) { - t.Errorf("Expecting: %+v, received: %+v", eOut, rcv) - } -} - func TestSize(t *testing.T) { - s := &StringSet{} + s := StringSet{} if rcv := s.Size(); rcv != 0 { t.Errorf("Expecting: 0, received %+v", rcv) } - s = &StringSet{data: map[string]struct{}{ + s = StringSet{ "test0": struct{}{}, "test1": struct{}{}, - "test2": struct{}{}}} + "test2": struct{}{}} if rcv := s.Size(); rcv != 3 { t.Errorf("Expecting: 3, received %+v", rcv) } } func TestIntersect(t *testing.T) { - s1 := &StringSet{data: map[string]struct{}{ + s1 := StringSet{ "test0": struct{}{}, "test1": struct{}{}, - "test2": struct{}{}}} - s2 := &StringSet{data: map[string]struct{}{ + "test2": struct{}{}} + s2 := StringSet{ "test0": struct{}{}, "test2": struct{}{}, - "test3": struct{}{}}} - eOut := &StringSet{data: map[string]struct{}{ + "test3": struct{}{}} + eOut := StringSet{ "test0": struct{}{}, - "test2": struct{}{}}} + "test2": struct{}{}} s1.Intersect(s2) if !reflect.DeepEqual(eOut, s1) { t.Errorf("Expecting: %+v, received: %+v", eOut, s1) } - s1 = &StringSet{data: map[string]struct{}{ + s1 = StringSet{ "test0": struct{}{}, "test1": struct{}{}, - "test2": struct{}{}}} - s2 = &StringSet{data: map[string]struct{}{ + "test2": struct{}{}} + s2 = StringSet{ "test3": struct{}{}, "test4": struct{}{}, - "test5": struct{}{}}} + "test5": struct{}{}} s1.Intersect(s2) - eOut = &StringSet{data: map[string]struct{}{}} + eOut = make(StringSet) if !reflect.DeepEqual(eOut, s1) { t.Errorf("Expecting: %+v, received: %+v", eOut, s1) }