diff --git a/engine/filters.go b/engine/filters.go index bb4bb0aea..aee231111 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -32,6 +32,7 @@ import ( const ( MetaString = "*string" MetaPrefix = "*prefix" + MetaSuffix = "*suffix" MetaTimings = "*timings" MetaRSR = "*rsr" MetaStatS = "*stats" @@ -146,17 +147,17 @@ func (f *Filter) Compile() (err error) { } func NewFilterRule(rfType, fieldName string, vals []string) (*FilterRule, error) { - if !utils.IsSliceMember([]string{MetaString, MetaPrefix, + if !utils.IsSliceMember([]string{MetaString, MetaPrefix, MetaSuffix, MetaTimings, MetaRSR, MetaStatS, MetaDestinations, MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("Unsupported filter Type: %s", rfType) } - if fieldName == "" && utils.IsSliceMember([]string{MetaString, - MetaPrefix, MetaTimings, MetaDestinations, MetaLessThan, + if fieldName == "" && utils.IsSliceMember([]string{MetaString, MetaPrefix, MetaSuffix, + MetaTimings, MetaDestinations, MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("FieldName is mandatory for Type: %s", rfType) } - if len(vals) == 0 && utils.IsSliceMember([]string{MetaString, MetaPrefix, + if len(vals) == 0 && utils.IsSliceMember([]string{MetaString, MetaPrefix, MetaSuffix, MetaTimings, MetaRSR, MetaDestinations, MetaDestinations, MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual}, rfType) { return nil, fmt.Errorf("Values is mandatory for Type: %s", rfType) @@ -222,6 +223,8 @@ func (fltr *FilterRule) Pass(dP config.DataProvider, rpcClnt rpcclient.RpcClient return fltr.passString(dP) case MetaPrefix: return fltr.passStringPrefix(dP) + case MetaSuffix: + return fltr.passStringSuffix(dP) case MetaTimings: return fltr.passTimings(dP) case MetaDestinations: @@ -269,6 +272,22 @@ func (fltr *FilterRule) passStringPrefix(dP config.DataProvider) (bool, error) { return false, nil } +func (fltr *FilterRule) passStringSuffix(dP config.DataProvider) (bool, error) { + strVal, err := dP.FieldAsString(strings.Split(fltr.FieldName, utils.NestingSep)) + if err != nil { + if err == utils.ErrNotFound { + return false, nil + } + return false, err + } + for _, prfx := range fltr.Values { + if strings.HasSuffix(strVal, prfx) { + return true, nil + } + } + return false, nil +} + // ToDo when Timings will be available in DataDb func (fltr *FilterRule) passTimings(dP config.DataProvider) (bool, error) { return false, utils.ErrNotImplemented diff --git a/engine/filters_test.go b/engine/filters_test.go index 2263a5d18..985af3d02 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -42,9 +42,17 @@ func TestFilterPassString(t *testing.T) { } func TestFilterPassStringPrefix(t *testing.T) { - cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "dan", Destination: "+4986517174963", - TimeStart: time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC), TimeEnd: time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC), - DurationIndex: 132 * time.Second, ExtraFields: map[string]string{"navigation": "off"}} + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "dan", + Destination: "+4986517174963", + TimeStart: time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC), + TimeEnd: time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC), + DurationIndex: 132 * time.Second, + ExtraFields: map[string]string{"navigation": "off"}, + } rf := &FilterRule{Type: MetaPrefix, FieldName: "Category", Values: []string{"call"}} if passes, err := rf.passStringPrefix(cd); err != nil { t.Error(err) @@ -83,6 +91,56 @@ func TestFilterPassStringPrefix(t *testing.T) { } } +func TestFilterPassStringSuffix(t *testing.T) { + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "dan", + Destination: "+4986517174963", + TimeStart: time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC), + TimeEnd: time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC), + DurationIndex: 132 * time.Second, + ExtraFields: map[string]string{"navigation": "off"}, + } + rf := &FilterRule{Type: MetaSuffix, FieldName: "Category", Values: []string{"call"}} + if passes, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if !passes { + t.Error("Not passes filter") + } + rf = &FilterRule{Type: MetaSuffix, FieldName: "Category", Values: []string{"premium"}} + if passes, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if passes { + t.Error("Passes filter") + } + rf = &FilterRule{Type: MetaSuffix, FieldName: "Destination", Values: []string{"963"}} + if passes, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if !passes { + t.Error("Not passes filter") + } + rf = &FilterRule{Type: MetaSuffix, FieldName: "Destination", Values: []string{"4966"}} + if passes, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if passes { + t.Error("Passes filter") + } + rf = &FilterRule{Type: MetaSuffix, FieldName: "navigation", Values: []string{"off"}} + if passes, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if !passes { + t.Error("Not passes filter") + } + rf = &FilterRule{Type: MetaSuffix, FieldName: "nonexisting", Values: []string{"off"}} + if passing, err := rf.passStringSuffix(cd); err != nil { + t.Error(err) + } else if passing { + t.Error("Passes filter") + } +} + func TestFilterPassRSRFields(t *testing.T) { cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "dan", Destination: "+4986517174963", @@ -223,6 +281,14 @@ func TestFilterNewRequestFilter(t *testing.T) { if !reflect.DeepEqual(erf, rf) { t.Errorf("Expecting: %+v, received: %+v", erf, rf) } + rf, err = NewFilterRule(MetaSuffix, "MetaSuffix", []string{"stringSuffix"}) + if err != nil { + t.Errorf("Error: %+v", err) + } + erf = &FilterRule{Type: MetaSuffix, FieldName: "MetaSuffix", Values: []string{"stringSuffix"}} + if !reflect.DeepEqual(erf, rf) { + t.Errorf("Expecting: %+v, received: %+v", erf, rf) + } rf, err = NewFilterRule(MetaTimings, "MetaTimings", []string{""}) if err != nil { t.Errorf("Error: %+v", err) @@ -321,6 +387,18 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) } + if pass, err := filterS.Pass("cgrates.org", + []string{"*suffix:Account:07"}, config.NewNavigableMap(failEvent)); err != nil { + t.Errorf(err.Error()) + } else if pass { + t.Errorf("Expecting: %+v, received: %+v", false, pass) + } + if pass, err := filterS.Pass("cgrates.org", + []string{"*suffix:Account:07"}, config.NewNavigableMap(passEvent)); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: %+v, received: %+v", true, pass) + } failEvent = map[string]interface{}{ "Tenant": "anotherTenant.org", } @@ -461,7 +539,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { utils.Usage: "1m20s", } if pass, err := filterS.Pass("cgrates.org", - []string{"*string:Account:1003", "*prefix:Destination:10", "*rsr::~Destination(1002)"}, config.NewNavigableMap(ev)); err != nil { + []string{"*string:Account:1003", "*prefix:Destination:10", "*suffix:Subject:03", "*rsr::~Destination(1002)"}, + config.NewNavigableMap(ev)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass)