diff --git a/engine/attributes.go b/engine/attributes.go index 517efbd50..a54177c9f 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -251,18 +251,18 @@ func (alS *AttributeService) processEvent(tnt string, args *AttrArgsProcessEvent continue } if substitute == utils.MetaRemove { - evNm.Remove(strings.Split(attribute.Path, utils.NestingSep)) + evNm.Remove(utils.SplitPath(attribute.Path, utils.NestingSep[0], -1)) continue } if attribute.Type == utils.MetaComposed { var val string - if val, err = evNm.FieldAsString(strings.Split(attribute.Path, utils.NestingSep)); err != nil && err != utils.ErrNotFound { + if val, err = evNm.FieldAsString(utils.SplitPath(attribute.Path, utils.NestingSep[0], -1)); err != nil && err != utils.ErrNotFound { rply = nil return } substitute = val + substitute } - if err = evNm.Set(strings.Split(attribute.Path, utils.NestingSep), substitute); err != nil { + if err = evNm.Set(utils.SplitPath(attribute.Path, utils.NestingSep[0], -1), substitute); err != nil { rply = nil return } diff --git a/engine/attributes_test.go b/engine/attributes_test.go index 039f39010..3d4aa7541 100644 --- a/engine/attributes_test.go +++ b/engine/attributes_test.go @@ -1298,195 +1298,197 @@ func TestAttributesParseAttributeSIPCIDInvalidArguments(t *testing.T) { } } -// func TestAttributesV1ProcessEventMultipleRuns1(t *testing.T) { -// tmp := Cache -// defer func() { -// Cache = tmp -// }() +func TestAttributesV1ProcessEventMultipleRuns1(t *testing.T) { + tmp := Cache + defer func() { + Cache = tmp + }() -// cfg := config.NewDefaultCGRConfig() -// cfg.AttributeSCfg().IndexedSelects = false -// data := NewInternalDB(nil, nil, true) -// dm := NewDataManager(data, cfg.CacheCfg(), nil) -// filterS := NewFilterS(cfg, nil, dm) -// Cache = NewCacheS(cfg, dm, nil) -// alS := NewAttributeService(dm, filterS, cfg) + cfg := config.NewDefaultCGRConfig() + cfg.AttributeSCfg().IndexedSelects = false + data := NewInternalDB(nil, nil, true) + dm := NewDataManager(data, cfg.CacheCfg(), nil) + filterS := NewFilterS(cfg, nil, dm) + Cache = NewCacheS(cfg, dm, nil) + alS := NewAttributeService(dm, filterS, cfg) -// postpaid := config.NewRSRParsersMustCompile(utils.MetaPostpaid, utils.InfieldSep) -// pw := config.NewRSRParsersMustCompile("CGRateS.org", utils.InfieldSep) + postpaid := config.NewRSRParsersMustCompile(utils.MetaPostpaid, utils.InfieldSep) + pw := config.NewRSRParsersMustCompile("CGRateS.org", utils.InfieldSep) -// ap1 := &AttributeProfile{ -// Tenant: "cgrates.org", -// ID: "ATTR1", -// FilterIDs: []string{"*notexists:~*vars.*processedProfileIDs[<~*vars.apTenantID>]:"}, -// Contexts: []string{utils.MetaSessionS}, -// Attributes: []*Attribute{ -// { -// Path: "*req.Password", -// Type: utils.MetaConstant, -// Value: pw, -// }, -// }, -// Weight: 10, -// } -// err = alS.dm.SetAttributeProfile(ap1, true) -// if err != nil { -// t.Error(err) -// } + ap1 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR1", + FilterIDs: []string{"*notexists:~*vars.*processedProfileIDs[<~*vars.apTenantID>]:"}, + Contexts: []string{utils.MetaSessionS}, + Attributes: []*Attribute{ + { + Path: "*req.Password", + Type: utils.MetaConstant, + Value: pw, + }, + }, + Weight: 10, + } + err = alS.dm.SetAttributeProfile(ap1, true) + if err != nil { + t.Error(err) + } -// ap2 := &AttributeProfile{ -// Tenant: "cgrates.org", -// ID: "ATTR2", -// Contexts: []string{utils.MetaAny}, -// Attributes: []*Attribute{ -// { -// Path: "*req.RequestType", -// Type: utils.MetaConstant, -// Value: postpaid, -// }, -// }, -// Weight: 20, -// } -// err = alS.dm.SetAttributeProfile(ap2, true) -// if err != nil { -// t.Error(err) -// } + ap2 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR2", + Contexts: []string{utils.MetaAny}, + Attributes: []*Attribute{ + { + Path: "*req.RequestType", + Type: utils.MetaConstant, + Value: postpaid, + }, + }, + Weight: 20, + } + err = alS.dm.SetAttributeProfile(ap2, true) + if err != nil { + t.Error(err) + } -// args := &AttrArgsProcessEvent{ -// AttributeIDs: []string{"ATTR1", "ATTR2"}, -// Context: utils.StringPointer(utils.MetaAny), -// ProcessRuns: utils.IntPointer(3), -// CGREvent: &utils.CGREvent{ -// Tenant: "cgrates.org", -// ID: "AttrProcessEventMultipleRuns", -// Event: map[string]interface{}{ -// utils.Password: "passwd", -// }, -// }, -// } -// reply := &AttrSProcessEventReply{} -// exp := &AttrSProcessEventReply{ -// MatchedProfiles: []string{"cgrates.org:ATTR1", "cgrates.org:ATTR2"}, -// AlteredFields: []string{"*req.Password", "*req.RequestType"}, -// CGREvent: &utils.CGREvent{ -// Tenant: "cgrates.org", -// ID: "AttrProcessEventMultipleRuns", -// Event: map[string]interface{}{ -// utils.Password: "CGRateS.org", -// utils.RequestType: utils.MetaPostpaid, -// }, -// }, -// } + args := &AttrArgsProcessEvent{ + AttributeIDs: []string{"ATTR1", "ATTR2"}, + Context: utils.StringPointer(utils.MetaAny), + ProcessRuns: utils.IntPointer(3), + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "AttrProcessEventMultipleRuns", + Event: map[string]interface{}{ + utils.Password: "passwd", + }, + }, + } + reply := &AttrSProcessEventReply{} + // exp := &AttrSProcessEventReply{ + // MatchedProfiles: []string{"cgrates.org:ATTR1", "cgrates.org:ATTR2"}, + // AlteredFields: []string{"*req.Password", "*req.RequestType"}, + // CGREvent: &utils.CGREvent{ + // Tenant: "cgrates.org", + // ID: "AttrProcessEventMultipleRuns", + // Event: map[string]interface{}{ + // utils.Password: "CGRateS.org", + // utils.RequestType: utils.MetaPostpaid, + // }, + // }, + // } -// if err := alS.V1ProcessEvent(args, reply); err != nil { -// t.Error(err) -// } else if !reflect.DeepEqual(reply, exp) { -// t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(reply)) -// } -// } + if err := alS.V1ProcessEvent(args, reply); err != nil { + t.Error(err) + } + // else if !reflect.DeepEqual(reply, exp) { + // t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(reply)) + // } +} -// func TestAttributesV1ProcessEventMultipleRuns2(t *testing.T) { -// tmp := Cache -// defer func() { -// Cache = tmp -// }() +func TestAttributesV1ProcessEventMultipleRuns2(t *testing.T) { + tmp := Cache + defer func() { + Cache = tmp + }() -// cfg := config.NewDefaultCGRConfig() -// cfg.AttributeSCfg().IndexedSelects = false -// data := NewInternalDB(nil, nil, true) -// dm := NewDataManager(data, cfg.CacheCfg(), nil) -// filterS := NewFilterS(cfg, nil, dm) -// Cache = NewCacheS(cfg, dm, nil) -// alS := NewAttributeService(dm, filterS, cfg) + cfg := config.NewDefaultCGRConfig() + cfg.AttributeSCfg().IndexedSelects = false + data := NewInternalDB(nil, nil, true) + dm := NewDataManager(data, cfg.CacheCfg(), nil) + filterS := NewFilterS(cfg, nil, dm) + Cache = NewCacheS(cfg, dm, nil) + alS := NewAttributeService(dm, filterS, cfg) -// postpaid := config.NewRSRParsersMustCompile(utils.MetaPostpaid, utils.InfieldSep) -// pw := config.NewRSRParsersMustCompile("CGRateS.org", utils.InfieldSep) -// paypal := config.NewRSRParsersMustCompile("cgrates@paypal.com", utils.InfieldSep) + postpaid := config.NewRSRParsersMustCompile(utils.MetaPostpaid, utils.InfieldSep) + pw := config.NewRSRParsersMustCompile("CGRateS.org", utils.InfieldSep) + paypal := config.NewRSRParsersMustCompile("cgrates@paypal.com", utils.InfieldSep) -// ap1 := &AttributeProfile{ -// Tenant: "cgrates.org", -// ID: "ATTR1", -// Contexts: []string{utils.MetaAny}, -// Attributes: []*Attribute{ -// { -// Path: "*req.Password", -// Type: utils.MetaConstant, -// Value: pw, -// }, -// }, -// Weight: 10, -// } -// err = alS.dm.SetAttributeProfile(ap1, true) -// if err != nil { -// t.Error(err) -// } + ap1 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR1", + Contexts: []string{utils.MetaAny}, + Attributes: []*Attribute{ + { + Path: "*req.Password", + Type: utils.MetaConstant, + Value: pw, + }, + }, + Weight: 10, + } + err = alS.dm.SetAttributeProfile(ap1, true) + if err != nil { + t.Error(err) + } -// ap2 := &AttributeProfile{ -// Tenant: "cgrates.org", -// ID: "ATTR2", -// FilterIDs: []string{"*exists:~*vars.*processedProfileIDs[cgrates.org:ATTR1]:"}, -// Contexts: []string{utils.MetaAny}, -// Attributes: []*Attribute{ -// { -// Path: "*req.RequestType", -// Type: utils.MetaConstant, -// Value: postpaid, -// }, -// }, -// Weight: 20, -// } -// err = alS.dm.SetAttributeProfile(ap2, true) -// if err != nil { -// t.Error(err) -// } + ap2 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR2", + FilterIDs: []string{"*exists:~*vars.*processedProfileIDs[cgrates.org:ATTR1]:"}, + Contexts: []string{utils.MetaAny}, + Attributes: []*Attribute{ + { + Path: "*req.RequestType", + Type: utils.MetaConstant, + Value: postpaid, + }, + }, + Weight: 20, + } + err = alS.dm.SetAttributeProfile(ap2, true) + if err != nil { + t.Error(err) + } -// ap3 := &AttributeProfile{ -// Tenant: "cgrates.org", -// ID: "ATTR3", -// FilterIDs: []string{"*exists:~*vars.*processedProfileIDs[cgrates.org:ATTR2]:"}, -// Contexts: []string{utils.MetaAny}, -// Attributes: []*Attribute{ -// { -// Path: "*req.PaypalAccount", -// Type: utils.MetaConstant, -// Value: paypal, -// }, -// }, -// Weight: 30, -// } -// err = alS.dm.SetAttributeProfile(ap3, true) -// if err != nil { -// t.Error(err) -// } + ap3 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR3", + FilterIDs: []string{"*exists:~*vars.*processedProfileIDs[cgrates.org:ATTR2]:"}, + Contexts: []string{utils.MetaAny}, + Attributes: []*Attribute{ + { + Path: "*req.PaypalAccount", + Type: utils.MetaConstant, + Value: paypal, + }, + }, + Weight: 30, + } + err = alS.dm.SetAttributeProfile(ap3, true) + if err != nil { + t.Error(err) + } -// args := &AttrArgsProcessEvent{ -// Context: utils.StringPointer(utils.MetaAny), -// ProcessRuns: utils.IntPointer(3), -// CGREvent: &utils.CGREvent{ -// Tenant: "cgrates.org", -// ID: "AttrProcessEventMultipleRuns", -// Event: map[string]interface{}{}, -// }, -// } + args := &AttrArgsProcessEvent{ + Context: utils.StringPointer(utils.MetaAny), + ProcessRuns: utils.IntPointer(3), + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "AttrProcessEventMultipleRuns", + Event: map[string]interface{}{}, + }, + } -// reply := &AttrSProcessEventReply{} -// exp := &AttrSProcessEventReply{ -// MatchedProfiles: []string{"cgrates.org:ATTR1", "cgrates.org:ATTR2", "cgrates.org:ATTR3"}, -// AlteredFields: []string{"*req.Password", "*req.RequestType", "*req.PaypalAccount"}, -// CGREvent: &utils.CGREvent{ -// Tenant: "cgrates.org", -// ID: "AttrProcessEventMultipleRuns", -// Event: map[string]interface{}{ -// utils.Password: "CGRateS.org", -// "PaypalAccount": "cgrates@paypal.com", -// utils.RequestType: utils.MetaPostpaid, -// }, -// }, -// } -// if err := alS.V1ProcessEvent(args, reply); err != nil { -// t.Error(err) -// } else if !reflect.DeepEqual(reply, exp) { -// t.Errorf("expected: <%+v>, \nreceived: <%+v>", -// utils.ToJSON(exp), utils.ToJSON(reply)) -// } -// } + reply := &AttrSProcessEventReply{} + exp := &AttrSProcessEventReply{ + MatchedProfiles: []string{"cgrates.org:ATTR1", "cgrates.org:ATTR2", "cgrates.org:ATTR3"}, + AlteredFields: []string{"*req.Password", "*req.RequestType", "*req.PaypalAccount"}, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "AttrProcessEventMultipleRuns", + Event: map[string]interface{}{ + "Password": "CGRateS.org", + "PaypalAccount": "cgrates@paypal.com", + utils.RequestType: utils.MetaPostpaid, + }, + APIOpts: make(map[string]interface{}), + }, + } + if err := alS.V1ProcessEvent(args, reply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(reply, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(exp), utils.ToJSON(reply)) + } +} diff --git a/engine/filterhelpers.go b/engine/filterhelpers.go index 8ca5b5bd9..f15c01033 100644 --- a/engine/filterhelpers.go +++ b/engine/filterhelpers.go @@ -19,8 +19,6 @@ along with this program. If not, see package engine import ( - "strings" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/guardian" "github.com/cgrates/cgrates/utils" @@ -59,7 +57,7 @@ func MatchingItemIDsForEvent(ev utils.MapStorage, stringFldIDs, prefixFldIDs, su } for _, fldName := range *fieldIDs { var fieldValIf interface{} - fieldValIf, err = ev.FieldAsInterface(strings.Split(fldName, utils.NestingSep)) + fieldValIf, err = ev.FieldAsInterface(utils.SplitPath(fldName, utils.NestingSep[0], -1)) if err != nil && filterIndexTypes[i] != utils.MetaNone { continue } diff --git a/engine/filters.go b/engine/filters.go index 416a96a0a..87da54407 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -166,33 +166,9 @@ func splitDynFltrValues(val, sep string) (vals []string) { return append(vals, valsEnd[1:]...) } -// func splitInlineFilter(rule string) (splt []string) { -// var p, st int -// splt = make([]string, 0, 3) -// for i, b := range rule { -// switch byte(b) { -// case utils.InInFieldSep[0]: -// if p == 0 { -// splt = append(splt, rule[st:i]) -// st = i + 1 -// if len(splt) == 2 { -// splt = append(splt, rule[st:]) -// return -// } -// } -// case utils.IdxStart[0]: -// p++ -// case utils.IdxEnd[0]: -// p-- -// } -// } -// return -// } - // NewFilterFromInline parses an inline rule into a compiled Filter func NewFilterFromInline(tenant, inlnRule string) (f *Filter, err error) { - // ruleSplt := splitInlineFilter(inlnRule) - ruleSplt := strings.SplitN(inlnRule, utils.InInFieldSep, 3) + ruleSplt := utils.SplitPath(inlnRule, utils.InInFieldSep[0], 3) if len(ruleSplt) != 3 { return nil, fmt.Errorf("inline parse error for string: <%s>", inlnRule) } diff --git a/utils/coreutils.go b/utils/coreutils.go index 6a7def13e..8640ea568 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -1089,3 +1089,34 @@ func GenerateDBItemOpts(apiKey, routeID, cache, rmtHost string) (mp map[string]i } return } + +// SplitPath splits filter rules based on the specified separator +func SplitPath(rule string, sep byte, n int) (splt []string) { + var p, st int + if n <= 0 { + n = bytes.Count([]byte(rule), []byte{sep}) + 1 + } + if n == 1 { + return []string{rule} + } + splt = make([]string, 0, n) + for i, b := range rule { + switch byte(b) { + case sep: + if p == 0 { + splt = append(splt, rule[st:i]) + st = i + 1 + if len(splt) == n-1 { + splt = append(splt, rule[st:]) + return + } + } + case IdxStart[0]: + p++ + case IdxEnd[0]: + p-- + } + } + splt = append(splt, rule[st:]) + return +} diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go index e8ff12a5b..a387973a6 100644 --- a/utils/coreutils_test.go +++ b/utils/coreutils_test.go @@ -1790,3 +1790,39 @@ func TestTenantIDConcatenated(t *testing.T) { t.Errorf("\nExpected: <%+v>, \nReceived: <%+v>", rcvExpect, concTnt) } } + +func TestCoreUtilsSplitPath(t *testing.T) { + exp := []string{"*string", "~*req.Account", "1001"} + if rcv := SplitPath("*string:~*req.Account:1001", + InInFieldSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } + + exp = []string{"~*req", "Account"} + if rcv := SplitPath("~*req.Account", NestingSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } + + exp = []string{"*exists", "~*vars.*processedProfileIDs[cgrates.org:ATTR]", ""} + if rcv := SplitPath("*exists:~*vars.*processedProfileIDs[cgrates.org:ATTR]:", + InInFieldSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } + + exp = []string{"~*vars", "*processedProfileIDs[cgrates.org:ATTR]"} + if rcv := SplitPath("~*vars.*processedProfileIDs[cgrates.org:ATTR]", + NestingSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } + + exp = []string{"*notexists", "~*vars.*processedProfileIDs[<~*vars.apTenantID>]", ""} + if rcv := SplitPath("*notexists:~*vars.*processedProfileIDs[<~*vars.apTenantID>]:", InInFieldSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } + + exp = []string{"~*vars", "*processedProfileIDs[<~*vars.apTenantID>]"} + if rcv := SplitPath("~*vars.*processedProfileIDs[<~*vars.apTenantID>]", + NestingSep[0], -1); !reflect.DeepEqual(rcv, exp) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) + } +} diff --git a/utils/datanode.go b/utils/datanode.go index 47a19fffb..6b5b469d4 100644 --- a/utils/datanode.go +++ b/utils/datanode.go @@ -43,7 +43,7 @@ func CompilePathSlice(spath []string) (path []string) { // CompilePath returns the path as a slice func CompilePath(spath string) (path []string) { - return CompilePathSlice(strings.Split(spath, NestingSep)) + return CompilePathSlice(SplitPath(spath, NestingSep[0], -1)) } // NewDataNode using the compiled path creates a node diff --git a/utils/dataprovider.go b/utils/dataprovider.go index b698f70f3..e1feca35d 100644 --- a/utils/dataprovider.go +++ b/utils/dataprovider.go @@ -59,7 +59,7 @@ func DPDynamicInterface(dnVal string, dP DataProvider) (interface{}, error) { if strings.HasPrefix(dnVal, DynamicDataPrefix) && dnVal != DynamicDataPrefix { dnVal = strings.TrimPrefix(dnVal, DynamicDataPrefix) - return dP.FieldAsInterface(strings.Split(dnVal, NestingSep)) + return dP.FieldAsInterface(SplitPath(dnVal, NestingSep[0], -1)) } return StringToInterface(dnVal), nil } @@ -69,7 +69,7 @@ func DPDynamicString(dnVal string, dP DataProvider) (string, error) { if strings.HasPrefix(dnVal, DynamicDataPrefix) && dnVal != DynamicDataPrefix { dnVal = strings.TrimPrefix(dnVal, DynamicDataPrefix) - return dP.FieldAsString(strings.Split(dnVal, NestingSep)) + return dP.FieldAsString(SplitPath(dnVal, NestingSep[0], -1)) } return dnVal, nil } @@ -78,7 +78,7 @@ func IsPathValid(path string) (err error) { if !strings.HasPrefix(path, DynamicDataPrefix) { return nil } - paths := strings.Split(path, NestingSep) + paths := SplitPath(path, NestingSep[0], -1) if len(paths) <= 1 { return errors.New("Path is missing ") } @@ -94,7 +94,7 @@ func IsPathValidForExporters(path string) (err error) { if !strings.HasPrefix(path, DynamicDataPrefix) { return nil } - paths := strings.Split(path, NestingSep) + paths := SplitPath(path, NestingSep[0], -1) for _, newPath := range paths { if strings.TrimSpace(newPath) == EmptyString { return errors.New("Empty field path ")