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