mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Add support for *virt export type in EEs
This commit is contained in:
@@ -320,7 +320,7 @@ var possibleReaderTypes = utils.NewStringSet([]string{utils.MetaFileCSV,
|
||||
|
||||
var possibleExporterTypes = utils.NewStringSet([]string{utils.MetaFileCSV, utils.META_NONE, utils.MetaFileFWV,
|
||||
utils.MetaHTTPPost, utils.MetaHTTPjsonMap, utils.MetaAMQPjsonMap, utils.MetaAMQPV1jsonMap, utils.MetaSQSjsonMap,
|
||||
utils.MetaKafkajsonMap, utils.MetaS3jsonMap})
|
||||
utils.MetaKafkajsonMap, utils.MetaS3jsonMap, utils.MetaVirt})
|
||||
|
||||
func (cfg *CGRConfig) LazySanityCheck() {
|
||||
for _, cdrePrfl := range cfg.cdrsCfg.OnlineCDRExports {
|
||||
|
||||
@@ -205,6 +205,9 @@ func (eeC *EventExporterCfg) loadFromJsonCfg(jsnEec *EventExporterJsonCfg, separ
|
||||
case utils.MetaTrl:
|
||||
eeC.trailerFields = append(eeC.trailerFields, field)
|
||||
}
|
||||
if strings.HasPrefix(field.GetPathSlice()[0], utils.MetaCache) { // special cache when loading fields that contains *cache in path
|
||||
eeC.contentFields = append(eeC.contentFields, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -298,6 +298,46 @@
|
||||
{"tag": "Cost", "path": "*exp.Cost", "type": "*variable", "value": "~*req.Cost{*round:4}"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "RouteExporter", // this exporter will set the Cost Account and RunID in cache so we can use it in other exports
|
||||
"type": "*virt",
|
||||
"tenant": "cgrates.org",
|
||||
"attempts": 1,
|
||||
"filters": ["*string:~*req.ExporterUsed:RouteExporter"],
|
||||
"fields":[
|
||||
{"tag": "Cost", "path": "*cache[~*req.CGRID|~*req.RunID|-Cost]", "type": "*variable",
|
||||
"value": "~*req.Cost", "rounding_decimals": 4},
|
||||
{"tag": "Account", "path": "*cache[~*req.CGRID|~*req.RunID|-Account]", "type": "*variable", "value": "~*req.Account"},
|
||||
{"tag": "RunID", "path": "*cache[~*req.CGRID|~*req.RunID|-RunID]", "type": "*variable", "value": "~*req.RunID"},
|
||||
{"tag": "CustomVariable", "path": "*cache[~*req.CGRID|~*req.RunID|-CustomVariable]",
|
||||
"type": "*variable", "value": "CustomValue"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "CSVExporterFromVirt",
|
||||
"type": "*file_csv",
|
||||
"export_path": "/tmp/testCSVfromVirt",
|
||||
"tenant": "cgrates.org",
|
||||
"flags": ["*attributes"],
|
||||
"attribute_context": "customContext",
|
||||
"attempts": 1,
|
||||
"field_separator": ",",
|
||||
"filters": ["*string:~*req.ExporterUsed:CSVExporterFromVirt"],
|
||||
"fields":[
|
||||
{"tag": "CGRID", "path": "*exp.CGRID", "type": "*variable", "value": "~*req.CGRID"},
|
||||
{"tag": "RunID", "path": "*exp.RunID", "type": "*variable", "value": "~*req.RunID"},
|
||||
{"tag": "OriginID", "path": "*exp.OriginID", "type": "*variable", "value": "~*req.OriginID"},
|
||||
{"tag": "Tenant", "path": "*exp.Tenant", "type": "*variable", "value": "~*req.Tenant"},
|
||||
{"tag": "Account", "path": "*exp.Account", "type": "*variable", "value": "~*req.Account"},
|
||||
{"tag": "Cost", "path": "*exp.Cost", "type": "*variable", "value": "~*req.Cost", "rounding_decimals": 4},
|
||||
{"tag": "SupplierCustomVariable","filters": ["*exists:*cache[~*req.CGRID|~*req.RunID|-CustomVariable]:"],
|
||||
"path": "*exp.SupplierCustomVariable", "type": "*variable", "value": "~*cache[~*req.CGRID|~*req.RunID|-CustomVariable]"},
|
||||
{"tag": "SupplierCost","filters": ["*exists:*cache[~*req.CGRID|~*req.RunID|-Cost]:"],
|
||||
"path": "*exp.SupplierCost", "type": "*variable", "value": "~*cache[~*req.CGRID|~*req.RunID|-Cost]"},
|
||||
{"tag": "SupplierRun","filters": ["*exists:*cache[~*req.CGRID|~*req.RunID|-RunID]:"],
|
||||
"path": "*exp.SupplierRun", "type": "*variable", "value": "~*cache[~*req.CGRID|~*req.RunID|-RunID]"},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ func NewEventExporter(cgrCfg *config.CGRConfig, cfgIdx int, filterS *engine.Filt
|
||||
return NewHTTPPostEe(cgrCfg, cfgIdx, filterS, dc)
|
||||
case utils.MetaHTTPjsonMap, utils.MetaAMQPjsonMap, utils.MetaAMQPV1jsonMap, utils.MetaSQSjsonMap, utils.MetaKafkajsonMap, utils.MetaS3jsonMap:
|
||||
return NewHTTPJsonMapEe(cgrCfg, cfgIdx, filterS, dc)
|
||||
case utils.MetaVirt:
|
||||
return NewVirtualExporter(cgrCfg, cfgIdx, filterS, dc)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported exporter type: <%s>", cgrCfg.EEsCfg().Exporters[cfgIdx].Type)
|
||||
}
|
||||
|
||||
@@ -127,11 +127,12 @@ func (eeR *EventExporterRequest) FieldAsString(fldPath []string) (val string, er
|
||||
func (eeR *EventExporterRequest) SetFields(tplFlds []*config.FCTemplate) (err error) {
|
||||
for _, tplFld := range tplFlds {
|
||||
if pass, err := eeR.filterS.Pass(eeR.tnt,
|
||||
tplFld.Filters, eeR); err != nil {
|
||||
tplFld.Filters, eeR.dynamicProvider); err != nil {
|
||||
return err
|
||||
} else if !pass {
|
||||
continue
|
||||
}
|
||||
|
||||
var out interface{}
|
||||
out, err = eeR.ParseField(tplFld)
|
||||
if err != nil {
|
||||
@@ -157,7 +158,6 @@ func (eeR *EventExporterRequest) SetFields(tplFlds []*config.FCTemplate) (err er
|
||||
} else {
|
||||
itmPath = fullPath.PathItems.Slice()[1:]
|
||||
}
|
||||
|
||||
nMItm := &config.NMItem{Data: out, Path: itmPath, Config: tplFld}
|
||||
switch tplFld.Type {
|
||||
case utils.META_COMPOSED:
|
||||
|
||||
@@ -49,7 +49,7 @@ func newRPCClient(cfg *config.ListenCfg) (c *rpc.Client, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
var exportPath = []string{"/tmp/testCSV", "/tmp/testComposedCSV", "/tmp/testFWV", "/tmp/testCSVMasked"}
|
||||
var exportPath = []string{"/tmp/testCSV", "/tmp/testComposedCSV", "/tmp/testFWV", "/tmp/testCSVMasked", "/tmp/testCSVfromVirt"}
|
||||
|
||||
func testCreateDirectory(t *testing.T) {
|
||||
for _, dir := range exportPath {
|
||||
|
||||
118
ees/virtualEe.go
Normal file
118
ees/virtualEe.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OerS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package ees
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func NewVirtualExporter(cgrCfg *config.CGRConfig, cfgIdx int, filterS *engine.FilterS,
|
||||
dc utils.MapStorage) (vEe *VirtualEe, err error) {
|
||||
dc[utils.ExportID] = cgrCfg.EEsCfg().Exporters[cfgIdx].ID
|
||||
vEe = &VirtualEe{id: cgrCfg.EEsCfg().Exporters[cfgIdx].ID,
|
||||
cgrCfg: cgrCfg, cfgIdx: cfgIdx, filterS: filterS, dc: dc}
|
||||
err = vEe.init()
|
||||
return
|
||||
}
|
||||
|
||||
// VirtualEe implements EventExporter interface for .csv files
|
||||
type VirtualEe struct {
|
||||
id string
|
||||
cgrCfg *config.CGRConfig
|
||||
cfgIdx int // index of config instance within ERsCfg.Readers
|
||||
filterS *engine.FilterS
|
||||
sync.RWMutex
|
||||
dc utils.MapStorage
|
||||
}
|
||||
|
||||
// init will create all the necessary dependencies, including opening the file
|
||||
func (vEe *VirtualEe) init() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ID returns the identificator of this exporter
|
||||
func (vEe *VirtualEe) ID() string {
|
||||
return vEe.id
|
||||
}
|
||||
|
||||
// OnEvicted implements EventExporter, doing the cleanup before exit
|
||||
func (vEe *VirtualEe) OnEvicted(_ string, _ interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// ExportEvent implements EventExporter
|
||||
func (vEe *VirtualEe) ExportEvent(cgrEv *utils.CGREvent) (err error) {
|
||||
vEe.Lock()
|
||||
defer vEe.Unlock()
|
||||
|
||||
vEe.dc[utils.NumberOfEvents] = vEe.dc[utils.NumberOfEvents].(int) + 1
|
||||
|
||||
req := utils.MapStorage{}
|
||||
for k, v := range cgrEv.Event {
|
||||
req[k] = v
|
||||
}
|
||||
eeReq := NewEventExporterRequest(req, vEe.dc, cgrEv.Tenant, vEe.cgrCfg.GeneralCfg().DefaultTimezone,
|
||||
vEe.filterS)
|
||||
if err = eeReq.SetFields(vEe.cgrCfg.EEsCfg().Exporters[vEe.cfgIdx].ContentFields()); err != nil {
|
||||
vEe.dc[utils.NegativeExports].(utils.StringSet).Add(cgrEv.ID)
|
||||
return
|
||||
}
|
||||
if aTime, err := cgrEv.FieldAsTime(utils.AnswerTime, vEe.cgrCfg.GeneralCfg().DefaultTimezone); err == nil {
|
||||
if vEe.dc[utils.FirstEventATime].(time.Time).IsZero() || vEe.dc[utils.FirstEventATime].(time.Time).Before(aTime) {
|
||||
vEe.dc[utils.FirstEventATime] = aTime
|
||||
}
|
||||
if aTime.After(vEe.dc[utils.LastEventATime].(time.Time)) {
|
||||
vEe.dc[utils.LastEventATime] = aTime
|
||||
}
|
||||
}
|
||||
if oID, err := cgrEv.FieldAsInt64(utils.OrderID); err == nil {
|
||||
if vEe.dc[utils.FirstExpOrderID].(int64) > oID || vEe.dc[utils.FirstExpOrderID].(int64) == 0 {
|
||||
vEe.dc[utils.FirstExpOrderID] = oID
|
||||
}
|
||||
if vEe.dc[utils.LastExpOrderID].(int64) < oID {
|
||||
vEe.dc[utils.LastExpOrderID] = oID
|
||||
}
|
||||
}
|
||||
if cost, err := cgrEv.FieldAsFloat64(utils.Cost); err == nil {
|
||||
vEe.dc[utils.TotalCost] = vEe.dc[utils.TotalCost].(float64) + cost
|
||||
}
|
||||
if tor, err := cgrEv.FieldAsString(utils.ToR); err == nil {
|
||||
if usage, err := cgrEv.FieldAsDuration(utils.Usage); err == nil {
|
||||
switch tor {
|
||||
case utils.VOICE:
|
||||
vEe.dc[utils.TotalDuration] = vEe.dc[utils.TotalDuration].(time.Duration) + usage
|
||||
case utils.SMS:
|
||||
vEe.dc[utils.TotalSMSUsage] = vEe.dc[utils.TotalSMSUsage].(time.Duration) + usage
|
||||
case utils.MMS:
|
||||
vEe.dc[utils.TotalMMSUsage] = vEe.dc[utils.TotalMMSUsage].(time.Duration) + usage
|
||||
case utils.GENERIC:
|
||||
vEe.dc[utils.TotalGenericUsage] = vEe.dc[utils.TotalGenericUsage].(time.Duration) + usage
|
||||
case utils.DATA:
|
||||
vEe.dc[utils.TotalDataUsage] = vEe.dc[utils.TotalDataUsage].(time.Duration) + usage
|
||||
}
|
||||
}
|
||||
}
|
||||
vEe.dc[utils.PositiveExports].(utils.StringSet).Add(cgrEv.ID)
|
||||
return
|
||||
}
|
||||
192
ees/virtual_ee_it_test.go
Normal file
192
ees/virtual_ee_it_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// +build integration
|
||||
|
||||
/*
|
||||
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package ees
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
)
|
||||
|
||||
var (
|
||||
virtConfigDir string
|
||||
virtCfgPath string
|
||||
virtCfg *config.CGRConfig
|
||||
virtRpc *rpc.Client
|
||||
|
||||
sTestsVirt = []func(t *testing.T){
|
||||
testCreateDirectory,
|
||||
testVirtLoadConfig,
|
||||
testVirtResetDataDB,
|
||||
testVirtResetStorDb,
|
||||
testVirtStartEngine,
|
||||
testVirtRPCConn,
|
||||
testVirtExportSupplierEvent,
|
||||
testVirtExportEvents,
|
||||
testVirtVerifyExports,
|
||||
testStopCgrEngine,
|
||||
testCleanDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
func TestVirtualExport(t *testing.T) {
|
||||
virtConfigDir = "ees"
|
||||
for _, stest := range sTestsVirt {
|
||||
t.Run(virtConfigDir, stest)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtLoadConfig(t *testing.T) {
|
||||
var err error
|
||||
virtCfgPath = path.Join(*dataDir, "conf", "samples", virtConfigDir)
|
||||
if virtCfg, err = config.NewCGRConfigFromPath(virtCfgPath); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtResetDataDB(t *testing.T) {
|
||||
if err := engine.InitDataDb(virtCfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtResetStorDb(t *testing.T) {
|
||||
if err := engine.InitStorDb(virtCfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtStartEngine(t *testing.T) {
|
||||
if _, err := engine.StopStartEngine(virtCfgPath, *waitRater); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtRPCConn(t *testing.T) {
|
||||
var err error
|
||||
virtRpc, err = newRPCClient(virtCfg.ListenCfg())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testVirtExportSupplierEvent(t *testing.T) {
|
||||
supplierEvent := &utils.CGREventWithOpts{
|
||||
CGREvent: &utils.CGREvent{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "supplierEvent",
|
||||
Time: utils.TimePointer(time.Now()),
|
||||
Event: map[string]interface{}{
|
||||
utils.CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()),
|
||||
utils.ToR: utils.VOICE,
|
||||
utils.OriginID: "dsafdsaf",
|
||||
utils.OriginHost: "192.168.1.1",
|
||||
utils.RequestType: utils.META_RATED,
|
||||
utils.Tenant: "cgrates.org",
|
||||
utils.Category: "call",
|
||||
utils.Account: "1001",
|
||||
utils.Subject: "1001",
|
||||
utils.Destination: "1002",
|
||||
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
|
||||
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
utils.Usage: time.Duration(10) * time.Second,
|
||||
utils.RunID: "SupplierRun",
|
||||
utils.Cost: 1.23,
|
||||
"ExporterUsed": "RouteExporter",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var reply string
|
||||
if err := virtRpc.Call(utils.EventExporterSv1ProcessEvent, supplierEvent, &reply); err != nil {
|
||||
t.Error(err)
|
||||
} else if reply != utils.OK {
|
||||
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
func testVirtExportEvents(t *testing.T) {
|
||||
eventVoice := &utils.CGREventWithOpts{
|
||||
CGREvent: &utils.CGREvent{
|
||||
Tenant: "cgrates.org",
|
||||
ID: "voiceEvent",
|
||||
Time: utils.TimePointer(time.Now()),
|
||||
Event: map[string]interface{}{
|
||||
utils.CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()),
|
||||
utils.ToR: utils.VOICE,
|
||||
utils.OriginID: "dsafdsaf",
|
||||
utils.OriginHost: "192.168.1.1",
|
||||
utils.RequestType: utils.META_RATED,
|
||||
utils.Tenant: "cgrates.org",
|
||||
utils.Category: "call",
|
||||
utils.Account: "1001",
|
||||
utils.Subject: "1001",
|
||||
utils.Destination: "1002",
|
||||
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
|
||||
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
utils.Usage: time.Duration(10) * time.Second,
|
||||
utils.RunID: "SupplierRun",
|
||||
utils.Cost: 1.01,
|
||||
"ExporterUsed": "CSVExporterFromVirt",
|
||||
},
|
||||
},
|
||||
}
|
||||
var reply string
|
||||
if err := virtRpc.Call(utils.EventExporterSv1ProcessEvent, eventVoice, &reply); err != nil {
|
||||
t.Error(err)
|
||||
} else if reply != utils.OK {
|
||||
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func testVirtVerifyExports(t *testing.T) {
|
||||
var files []string
|
||||
err := filepath.Walk("/tmp/testCSVfromVirt/", func(path string, info os.FileInfo, err error) error {
|
||||
if strings.HasSuffix(path, utils.CSVSuffix) {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(files) != 1 {
|
||||
t.Errorf("Expected %+v, received: %+v", 1, len(files))
|
||||
}
|
||||
eCnt := "dbafe9c8614c785a65aabd116dd3959c3c56f7f6,SupplierRun,dsafdsaf,cgrates.org,1001,1.01,CustomValue,1.23,SupplierRun\n"
|
||||
if outContent1, err := ioutil.ReadFile(files[0]); err != nil {
|
||||
t.Error(err)
|
||||
} else if eCnt != string(outContent1) {
|
||||
t.Errorf("Expecting: \n<%q>, \nreceived: \n<%q>", eCnt, string(outContent1))
|
||||
}
|
||||
}
|
||||
@@ -549,7 +549,7 @@ func (dDP *dynamicDP) RemoteHost() net.Addr {
|
||||
|
||||
var initialDPPrefixes = utils.NewStringSet([]string{utils.MetaReq, utils.MetaVars,
|
||||
utils.MetaCgreq, utils.MetaCgrep, utils.MetaRep, utils.MetaCGRAReq,
|
||||
utils.MetaAct, utils.MetaEC})
|
||||
utils.MetaAct, utils.MetaEC, utils.MetaCache})
|
||||
|
||||
func (dDP *dynamicDP) FieldAsInterface(fldPath []string) (val interface{}, err error) {
|
||||
if len(fldPath) == 0 {
|
||||
|
||||
@@ -396,6 +396,7 @@ const (
|
||||
ActionsPoster = "act"
|
||||
CDRPoster = "cdr"
|
||||
MetaFileCSV = "*file_csv"
|
||||
MetaVirt = "*virt"
|
||||
MetaFileFWV = "*file_fwv"
|
||||
MetaFScsv = "*freeswitch_csv"
|
||||
Accounts = "Accounts"
|
||||
|
||||
@@ -43,7 +43,7 @@ func (ddp *DynamicDataProvider) FieldAsInterface(fldPath []string) (out interfac
|
||||
return val, nil
|
||||
}
|
||||
var newPath string
|
||||
if newPath, err = ddp.proccesFieldPath(path); err != nil { // proccess the path
|
||||
if newPath, err = ddp.processFieldPathForSet(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
|
||||
@@ -58,64 +58,10 @@ func (ddp *DynamicDataProvider) FieldAsInterface(fldPath []string) (out interfac
|
||||
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
|
||||
}
|
||||
|
||||
// GetFullFieldPath returns the full path for the
|
||||
func (ddp *DynamicDataProvider) GetFullFieldPath(fldPath string) (fpath *FullPath, err error) {
|
||||
var newPath string
|
||||
if newPath, err = ddp.proccesFieldPathForSet(fldPath); err != nil || newPath == EmptyString {
|
||||
if newPath, err = ddp.processFieldPathForSet(fldPath); err != nil || newPath == EmptyString {
|
||||
return
|
||||
}
|
||||
fpath = &FullPath{
|
||||
@@ -136,7 +82,7 @@ func (ddp DynamicDataProvider) FieldAsString(fldPath []string) (str string, err
|
||||
}
|
||||
|
||||
// does the same thing as ... but replaces [ with . if the value between [] is dynamic
|
||||
func (ddp *DynamicDataProvider) proccesFieldPathForSet(fldPath string) (newPath string, err error) {
|
||||
func (ddp *DynamicDataProvider) processFieldPathForSet(fldPath string) (newPath string, err error) {
|
||||
idx := strings.Index(fldPath, IdxStart)
|
||||
if idx == -1 {
|
||||
return // no proccessing requred
|
||||
|
||||
@@ -18,56 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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{
|
||||
@@ -163,7 +118,7 @@ func TestDynamicDataProviderProccesFieldPath2(t *testing.T) {
|
||||
},
|
||||
}
|
||||
ddp := NewDynamicDataProvider(dp)
|
||||
newpath, err := ddp.proccesFieldPathForSet("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID|_END].Something[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]")
|
||||
newpath, err := ddp.processFieldPathForSet("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID|_END].Something[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -171,21 +126,21 @@ func TestDynamicDataProviderProccesFieldPath2(t *testing.T) {
|
||||
if newpath != expectedPath {
|
||||
t.Errorf("Expected: %q,received %q", expectedPath, newpath)
|
||||
}
|
||||
_, err = ddp.proccesFieldPathForSet("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID|_END].Something[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END")
|
||||
_, err = ddp.processFieldPathForSet("~*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.proccesFieldPathForSet("~*cgrep.Stir[CHRG_")
|
||||
_, err = ddp.processFieldPathForSet("~*cgrep.Stir[CHRG_")
|
||||
if err != ErrWrongPath {
|
||||
t.Errorf("Expected error %s received %v", ErrWrongPath, err)
|
||||
}
|
||||
|
||||
_, err = ddp.proccesFieldPathForSet("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID2|_END]")
|
||||
_, err = ddp.processFieldPathForSet("~*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[1].ID2|_END]")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Expected error %s received %v", ErrNotFound, err)
|
||||
}
|
||||
newpath, err = ddp.proccesFieldPathForSet("~*cgrep.Stir[1]")
|
||||
newpath, err = ddp.processFieldPathForSet("~*cgrep.Stir[1]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -289,4 +244,13 @@ func TestDynamicDataProviderGetFullFieldPath(t *testing.T) {
|
||||
if newpath != nil {
|
||||
t.Errorf("Expected: %v,received %q", nil, newpath)
|
||||
}
|
||||
|
||||
newpath, err = ddp.GetFullFieldPath("*cgrep.Stir[CHRG_|~*cgrep.Routes.SortedRoutes[~*cgrep.BestRoute].ID|_END]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newpath == nil {
|
||||
t.Errorf("Expected: %v,received %q", nil, newpath)
|
||||
}
|
||||
fmt.Println(*newpath)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user