From b3ed751e28390a9fc6fd3acdf440b36a38c3c1d5 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 11 Jun 2018 18:12:15 +0200 Subject: [PATCH] FilterS.Pass over DataProvider interface --- agents/httpagent.go | 38 +++++++++++++++++++--- agents/libhttpagent.go | 19 +++++------ agents/librad.go | 4 +-- cmd/cgr-engine/cgr-engine.go | 17 +++++++--- engine/attributes.go | 4 +-- engine/calldesc.go | 21 ++++++++++++ engine/filters.go | 54 +++++++++++++++++-------------- engine/filters_test.go | 63 +++++++++++++++++++----------------- engine/resources.go | 3 +- engine/stats.go | 3 +- engine/suppliers.go | 8 ++--- engine/thresholds.go | 3 +- utils/dataprovider.go | 26 +++++++++++++++ utils/navigablemap.go | 28 +++++++++------- utils/navigablemap_test.go | 10 ++++-- 15 files changed, 200 insertions(+), 101 deletions(-) create mode 100644 utils/dataprovider.go diff --git a/agents/httpagent.go b/agents/httpagent.go index fc984f7c3..305a6722a 100644 --- a/agents/httpagent.go +++ b/agents/httpagent.go @@ -21,14 +21,17 @@ package agents import ( "fmt" "net/http" + "strconv" "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" "github.com/cgrates/rpcclient" ) // NewHttpAgent will construct a HTTPAgent func NewHTTPAgent(sessionS rpcclient.RpcClientConnection, + filterS *engine.FilterS, timezone, reqPayload, rplyPayload string, reqProcessors []*config.HttpAgntProcCfg) *HTTPAgent { return &HTTPAgent{sessionS: sessionS, timezone: timezone, @@ -39,6 +42,7 @@ func NewHTTPAgent(sessionS rpcclient.RpcClientConnection, // HTTPAgent is a handler for HTTP requests type HTTPAgent struct { sessionS rpcclient.RpcClientConnection + filterS *engine.FilterS timezone, reqPayload, rplyPayload string @@ -47,7 +51,7 @@ type HTTPAgent struct { // ServeHTTP implements http.Handler interface func (ha *HTTPAgent) ServeHTTP(w http.ResponseWriter, req *http.Request) { - dcdr, err := newHAReqDecoder(ha.reqPayload, req) // dcdr will provide information from request + dcdr, err := newHADataProvider(ha.reqPayload, req) // dcdr will provide information from request if err != nil { utils.Logger.Warning( fmt.Sprintf("<%s> error creating decoder: %s", @@ -63,7 +67,8 @@ func (ha *HTTPAgent) ServeHTTP(w http.ResponseWriter, req *http.Request) { procVars, rpl); lclProcessed { processed = lclProcessed } - if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) { + if err != nil || + (lclProcessed && !reqProcessor.ContinueOnSuccess) { break } } @@ -94,9 +99,34 @@ func (ha *HTTPAgent) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // processRequest represents one processor processing the request -func (ha *HTTPAgent) processRequest(reqProc *config.HttpAgntProcCfg, - dcdr httpAgentReqDecoder, procVars processorVars, +func (ha *HTTPAgent) processRequest(reqProcessor *config.HttpAgntProcCfg, + dP utils.DataProvider, procVars processorVars, reply *httpReplyFields) (processed bool, err error) { + tnt, err := dP.FieldAsString([]string{utils.Tenant}) + if err != nil { + return false, err + } + if pass, err := ha.filterS.Pass(tnt, reqProcessor.Filters, dP); err != nil { + return false, err + } else if !pass { + return false, nil + } + for k, v := range reqProcessor.Flags { // update procVars with flags from processor + procVars[k] = strconv.FormatBool(v) + } + if reqProcessor.DryRun { + utils.Logger.Info(fmt.Sprintf("<%s> DRY_RUN, RADIUS request: %s", utils.RadiusAgent, dP)) + utils.Logger.Info(fmt.Sprintf("<%s> DRY_RUN, process variabiles: %+v", utils.RadiusAgent, procVars)) + } + /* + cgrEv, err := radReqAsCGREvent(req, procVars, reqProcessor.Flags, reqProcessor.RequestFields) + if err != nil { + return false, err + } + if reqProcessor.DryRun { + utils.Logger.Info(fmt.Sprintf("<%s> DRY_RUN, CGREvent: %s", utils.RadiusAgent, utils.ToJSON(cgrEv))) + } + */ return } diff --git a/agents/libhttpagent.go b/agents/libhttpagent.go index 1d9b72a91..9183e3d54 100644 --- a/agents/libhttpagent.go +++ b/agents/libhttpagent.go @@ -21,12 +21,14 @@ package agents import ( "fmt" "net/http" + + "github.com/cgrates/cgrates/utils" ) // httpReplyField is one field written in HTTP reply type httpReplyField struct { - fldPath, - fldVal string + fldPath string + fldVal string } func newHTTPReplyFields() *httpReplyFields { @@ -42,19 +44,14 @@ type httpReplyFields struct { } // newHAReqDecoder produces decoders -func newHAReqDecoder(dcdType string, - req *http.Request) (rD httpAgentReqDecoder, err error) { - switch dcdType { +func newHADataProvider(dpType string, + req *http.Request) (dP utils.DataProvider, err error) { + switch dpType { default: - return nil, fmt.Errorf("unsupported decoder type <%s>", dcdType) + return nil, fmt.Errorf("unsupported decoder type <%s>", dpType) } } -// httpAgentReqDecoder will decode request values -type httpAgentReqDecoder interface { - getFieldVal(fldPath string) (interface{}, error) -} - // newHAReplyEncoder constructs a httpAgentReqDecoder based on encoder type func newHAReplyEncoder(encType string, w http.ResponseWriter) (rE httpAgentReplyEncoder, err error) { diff --git a/agents/librad.go b/agents/librad.go index 087b1088c..9f9ef8b8e 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -61,7 +61,7 @@ func (pv processorVars) valAsInterface(fldPath string) (val interface{}, err err err = errors.New("not found") return } - return utils.NavigableMap(pv).GetField(fldPath, utils.HIERARCHY_SEP) + return utils.NavigableMap(pv).FieldAsInterface(strings.Split(fldPath, utils.HIERARCHY_SEP)) } // valAsString returns the string value for fldName @@ -74,7 +74,7 @@ func (pv processorVars) valAsString(fldPath string) (val string, err error) { if !pv.hasVar(fldName) { return "", utils.ErrNotFoundNoCaps } - return utils.NavigableMap(pv).GetFieldAsString(fldPath, utils.HIERARCHY_SEP) + return utils.NavigableMap(pv).FieldAsString(strings.Split(fldPath, utils.HIERARCHY_SEP)) } // asV1AuthorizeArgs returns the arguments needed by SessionSv1.AuthorizeEvent diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 5b710849f..bc81ff444 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -351,7 +351,10 @@ func startKamAgent(internalSMGChan chan rpcclient.RpcClientConnection, exitChan exitChan <- true } -func startHTTPAgent(internalSMGChan chan rpcclient.RpcClientConnection, exitChan chan bool, server *utils.Server) { +func startHTTPAgent(internalSMGChan chan rpcclient.RpcClientConnection, + exitChan chan bool, server *utils.Server, filterSChan chan *engine.FilterS) { + filterS := <-filterSChan + filterSChan <- filterS utils.Logger.Info("Starting HTTP agent") var err error for _, agntCfg := range cfg.HttpAgentCfg() { @@ -369,7 +372,7 @@ func startHTTPAgent(internalSMGChan chan rpcclient.RpcClientConnection, exitChan } } server.RegisterHttpHandler(agntCfg.Url, - agents.NewHTTPAgent(sSConn, agntCfg.Timezone, agntCfg.RequestPayload, + agents.NewHTTPAgent(sSConn, filterS, agntCfg.Timezone, agntCfg.RequestPayload, agntCfg.ReplyPayload, agntCfg.RequestProcessors)) } exitChan <- true @@ -546,13 +549,17 @@ func startAttributeService(internalAttributeSChan chan rpcclient.RpcClientConnec aS, err := engine.NewAttributeService(dm, filterS, cfg.AttributeSCfg().StringIndexedFields, cfg.AttributeSCfg().PrefixIndexedFields) if err != nil { - utils.Logger.Crit(fmt.Sprintf("<%s> Could not init, error: %s", utils.AttributeS, err.Error())) + utils.Logger.Crit( + fmt.Sprintf("<%s> Could not init, error: %s", + utils.AttributeS, err.Error())) exitChan <- true return } go func() { if err := aS.ListenAndServe(exitChan); err != nil { - utils.Logger.Crit(fmt.Sprintf("<%s> Error: %s listening for packets", utils.AttributeS, err.Error())) + utils.Logger.Crit( + fmt.Sprintf("<%s> Error: %s listening for packets", + utils.AttributeS, err.Error())) } aS.Shutdown() exitChan <- true @@ -1170,7 +1177,7 @@ func main() { } if len(cfg.HttpAgentCfg()) != 0 { - go startHTTPAgent(internalSMGChan, exitChan, server) + go startHTTPAgent(internalSMGChan, exitChan, server, filterSChan) } // Start PubSubS service diff --git a/engine/attributes.go b/engine/attributes.go index 1ac7895c9..c0ff1b22f 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -90,8 +90,8 @@ func (alS *AttributeService) matchingAttributeProfilesForEvent(ev *utils.CGREven !aPrfl.ActivationInterval.IsActiveAtTime(*ev.Time) { // not active continue } - if pass, err := alS.filterS.PassFiltersForEvent(ev.Tenant, - ev.Event, aPrfl.FilterIDs); err != nil { + if pass, err := alS.filterS.Pass(ev.Tenant, aPrfl.FilterIDs, + utils.NavigableMap(ev.Event)); err != nil { return nil, err } else if !pass { continue diff --git a/engine/calldesc.go b/engine/calldesc.go index 64d995d10..4fd256bb6 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -1433,3 +1433,24 @@ func (cd *CallDescriptor) AccountSummary() *AccountSummary { } return cd.account.AsAccountSummary() } + +// FieldAsInterface is part of utils.DataProvider +func (cd *CallDescriptor) FieldAsInterface(fldPath []string) (fldVal interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + return utils.ReflectFieldInterface(cd, fldPath[0], utils.EXTRA_FIELDS) +} + +// FieldAsString is part of utils.DataProvider +func (cd *CallDescriptor) FieldAsString(fldPath []string) (fldVal string, err error) { + if len(fldPath) == 0 { + return "", utils.ErrNotFound + } + return utils.ReflectFieldAsString(cd, fldPath[0], utils.EXTRA_FIELDS) +} + +// String is part of utils.DataProvider +func (cd *CallDescriptor) String() string { + return utils.ToJSON(cd) +} diff --git a/engine/filters.go b/engine/filters.go index 4a50f56b3..cd65051aa 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -65,15 +65,19 @@ func (fS *FilterS) connStatS() (err error) { if fS.statSConns != nil { // connection was populated between locks return } - fS.statSConns, err = NewRPCPool(rpcclient.POOL_FIRST, fS.cfg.TLSClientKey, fS.cfg.TLSClientCerificate, - fS.cfg.ConnectAttempts, fS.cfg.Reconnects, fS.cfg.ConnectTimeout, fS.cfg.ReplyTimeout, - fS.cfg.FilterSCfg().StatSConns, fS.statSChan, fS.cfg.InternalTtl) + fS.statSConns, err = NewRPCPool(rpcclient.POOL_FIRST, + fS.cfg.TLSClientKey, fS.cfg.TLSClientCerificate, + fS.cfg.ConnectAttempts, fS.cfg.Reconnects, + fS.cfg.ConnectTimeout, fS.cfg.ReplyTimeout, + fS.cfg.FilterSCfg().StatSConns, + fS.statSChan, fS.cfg.InternalTtl) return } -// PassFiltersForEvent will check all filters wihin filterIDs and require them passing for event +// Pass will check all filters wihin filterIDs and require them passing for dataProvider // there should be at least one filter passing, ie: if filters are not active event will fail to pass -func (fS *FilterS) PassFiltersForEvent(tenant string, ev map[string]interface{}, filterIDs []string) (pass bool, err error) { +// receives the event as utils.DataProvider so we can accept undecoded data (ie: HttpRequest) +func (fS *FilterS) Pass(tenant string, filterIDs []string, ev utils.DataProvider) (pass bool, err error) { if len(filterIDs) == 0 { return true, nil } @@ -205,29 +209,29 @@ func (rf *FilterRule) CompileValues() (err error) { } // Pass is the method which should be used from outside. -func (fltr *FilterRule) Pass(req interface{}, rpcClnt rpcclient.RpcClientConnection) (bool, error) { +func (fltr *FilterRule) Pass(dP utils.DataProvider, rpcClnt rpcclient.RpcClientConnection) (bool, error) { switch fltr.Type { case MetaString: - return fltr.passString(req) + return fltr.passString(dP) case MetaPrefix: - return fltr.passStringPrefix(req) + return fltr.passStringPrefix(dP) case MetaTimings: - return fltr.passTimings(req) + return fltr.passTimings(dP) case MetaDestinations: - return fltr.passDestinations(req) + return fltr.passDestinations(dP) case MetaRSR: - return fltr.passRSR(req) + return fltr.passRSR(dP) case MetaStatS: - return fltr.passStatS(req, rpcClnt) + return fltr.passStatS(dP, rpcClnt) case MetaLessThan, MetaLessOrEqual, MetaGreaterThan, MetaGreaterOrEqual: - return fltr.passGreaterThan(req) + return fltr.passGreaterThan(dP) default: return false, utils.ErrNotImplemented } } -func (fltr *FilterRule) passString(req interface{}) (bool, error) { - strVal, err := utils.ReflectFieldAsString(req, fltr.FieldName, utils.EXTRA_FIELDS) +func (fltr *FilterRule) passString(dP utils.DataProvider) (bool, error) { + strVal, err := dP.FieldAsString(strings.Split(fltr.FieldName, utils.HIERARCHY_SEP)) if err != nil { if err == utils.ErrNotFound { return false, nil @@ -242,8 +246,8 @@ func (fltr *FilterRule) passString(req interface{}) (bool, error) { return false, nil } -func (fltr *FilterRule) passStringPrefix(req interface{}) (bool, error) { - strVal, err := utils.ReflectFieldAsString(req, fltr.FieldName, utils.EXTRA_FIELDS) +func (fltr *FilterRule) passStringPrefix(dP utils.DataProvider) (bool, error) { + strVal, err := dP.FieldAsString(strings.Split(fltr.FieldName, utils.HIERARCHY_SEP)) if err != nil { if err == utils.ErrNotFound { return false, nil @@ -259,12 +263,12 @@ func (fltr *FilterRule) passStringPrefix(req interface{}) (bool, error) { } // ToDo when Timings will be available in DataDb -func (fltr *FilterRule) passTimings(req interface{}) (bool, error) { +func (fltr *FilterRule) passTimings(dP utils.DataProvider) (bool, error) { return false, utils.ErrNotImplemented } -func (fltr *FilterRule) passDestinations(req interface{}) (bool, error) { - dst, err := utils.ReflectFieldAsString(req, fltr.FieldName, utils.EXTRA_FIELDS) +func (fltr *FilterRule) passDestinations(dP utils.DataProvider) (bool, error) { + dst, err := dP.FieldAsString(strings.Split(fltr.FieldName, utils.HIERARCHY_SEP)) if err != nil { if err == utils.ErrNotFound { return false, nil @@ -285,9 +289,9 @@ func (fltr *FilterRule) passDestinations(req interface{}) (bool, error) { return false, nil } -func (fltr *FilterRule) passRSR(req interface{}) (bool, error) { +func (fltr *FilterRule) passRSR(dP utils.DataProvider) (bool, error) { for _, rsrFld := range fltr.rsrFields { - fldIface, err := utils.ReflectFieldInterface(req, rsrFld.Id, utils.EXTRA_FIELDS) + fldIface, err := dP.FieldAsInterface(strings.Split(rsrFld.Id, utils.HIERARCHY_SEP)) if err != nil { if err == utils.ErrNotFound { return false, nil @@ -301,7 +305,7 @@ func (fltr *FilterRule) passRSR(req interface{}) (bool, error) { return false, nil } -func (fltr *FilterRule) passStatS(req interface{}, +func (fltr *FilterRule) passStatS(dP utils.DataProvider, stats rpcclient.RpcClientConnection) (bool, error) { if stats == nil || reflect.ValueOf(stats).IsNil() { return false, errors.New("Missing StatS information") @@ -326,8 +330,8 @@ func (fltr *FilterRule) passStatS(req interface{}, return false, nil } -func (fltr *FilterRule) passGreaterThan(req interface{}) (bool, error) { - fldIf, err := utils.ReflectFieldInterface(req, fltr.FieldName, utils.EXTRA_FIELDS) +func (fltr *FilterRule) passGreaterThan(dP utils.DataProvider) (bool, error) { + fldIf, err := dP.FieldAsInterface(strings.Split(fltr.FieldName, utils.HIERARCHY_SEP)) if err != nil { if err == utils.ErrNotFound { return false, nil diff --git a/engine/filters_test.go b/engine/filters_test.go index b5af3324d..7c5424595 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -84,9 +84,12 @@ func TestFilterPassStringPrefix(t *testing.T) { } func TestFilterPassRSRFields(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, err := NewFilterRule(MetaRSR, "", []string{"Tenant(~^cgr.*\\.org$)"}) if err != nil { t.Error(err) @@ -151,7 +154,7 @@ func TestFilterPassGreaterThan(t *testing.T) { if err != nil { t.Error(err) } - ev := map[string]interface{}{ + ev := utils.NavigableMap{ "ASR": 20, } if passes, err := rf.passGreaterThan(ev); err != nil { @@ -288,18 +291,18 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent := map[string]interface{}{ "Account": "1007", } - if _, err := filterS.PassFiltersForEvent("cgrates.org", - nil, []string{"*string:Account:1007:error"}); err == nil { + if _, err := filterS.Pass("cgrates.org", + []string{"*string:Account:1007:error"}, nil); err == nil { t.Errorf(err.Error()) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - failEvent, []string{"*string:Account:1007"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:Account:1007"}, utils.NavigableMap(failEvent)); err != nil { t.Errorf(err.Error()) } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent, []string{"*string:Account:1007"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:Account:1007"}, utils.NavigableMap(passEvent)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) @@ -310,14 +313,14 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ "Account": "1007", } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - failEvent, []string{"*prefix:Account:10"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*prefix:Account:10"}, utils.NavigableMap(failEvent)); err != nil { t.Errorf(err.Error()) } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent, []string{"*prefix:Account:10"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*prefix:Account:10"}, utils.NavigableMap(passEvent)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) @@ -328,14 +331,14 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ "Tenant": "cgrates.org", } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - failEvent, []string{"*rsr::Tenant(~^cgr.*\\.org$)"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*rsr::Tenant(~^cgr.*\\.org$)"}, utils.NavigableMap(failEvent)); err != nil { t.Errorf(err.Error()) } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent, []string{"*rsr::Tenant(~^cgr.*\\.org$)"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*rsr::Tenant(~^cgr.*\\.org$)"}, utils.NavigableMap(passEvent)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) @@ -348,14 +351,14 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ utils.Destination: "+4986517174963", } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - failEvent, []string{"*destinations:Destination:EU"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*destinations:Destination:EU"}, utils.NavigableMap(failEvent)); err != nil { t.Errorf(err.Error()) } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent, []string{"*destinations:Destination:EU_LANDLINE"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*destinations:Destination:EU_LANDLINE"}, utils.NavigableMap(passEvent)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) @@ -366,14 +369,14 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ utils.Weight: 20, } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - failEvent, []string{"*gte:Weight:20"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*gte:Weight:20"}, utils.NavigableMap(failEvent)); err != nil { t.Errorf(err.Error()) } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent, []string{"*gte:Weight:10"}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{"*gte:Weight:10"}, utils.NavigableMap(passEvent)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) @@ -400,14 +403,14 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { utils.Destination: "+4986517174963", utils.Weight: 20, } - if pass, err := filterS.PassFiltersForEvent("cgrates.org", - passEvent1, []string{}); err != nil { + if pass, err := filterS.Pass("cgrates.org", + []string{}, utils.NavigableMap(passEvent1)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - if pass, err := filterS.PassFiltersForEvent("itsyscom.com", - passEvent2, []string{}); err != nil { + if pass, err := filterS.Pass("itsyscom.com", + []string{}, utils.NavigableMap(passEvent2)); err != nil { t.Errorf(err.Error()) } else if !pass { t.Errorf("Expecting: %+v, received: %+v", true, pass) diff --git a/engine/resources.go b/engine/resources.go index 7ec18061b..fdf876c04 100644 --- a/engine/resources.go +++ b/engine/resources.go @@ -463,7 +463,8 @@ func (rS *ResourceService) matchingResourcesForEvent(ev *utils.CGREvent, usageTT !rPrf.ActivationInterval.IsActiveAtTime(*ev.Time) { // not active continue } - if pass, err := rS.filterS.PassFiltersForEvent(ev.Tenant, ev.Event, rPrf.FilterIDs); err != nil { + if pass, err := rS.filterS.Pass(ev.Tenant, rPrf.FilterIDs, + utils.NavigableMap(ev.Event)); err != nil { return nil, err } else if !pass { continue diff --git a/engine/stats.go b/engine/stats.go index 494818fcf..0229b85e1 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -168,7 +168,8 @@ func (sS *StatService) matchingStatQueuesForEvent(ev *utils.CGREvent) (sqs StatQ !sqPrfl.ActivationInterval.IsActiveAtTime(*ev.Time) { // not active continue } - if pass, err := sS.filterS.PassFiltersForEvent(ev.Tenant, ev.Event, sqPrfl.FilterIDs); err != nil { + if pass, err := sS.filterS.Pass(ev.Tenant, sqPrfl.FilterIDs, + utils.NavigableMap(ev.Event)); err != nil { return nil, err } else if !pass { continue diff --git a/engine/suppliers.go b/engine/suppliers.go index 7cf2569f9..e46eaed09 100644 --- a/engine/suppliers.go +++ b/engine/suppliers.go @@ -136,8 +136,8 @@ func (spS *SupplierService) matchingSupplierProfilesForEvent(ev *utils.CGREvent) !splPrfl.ActivationInterval.IsActiveAtTime(*ev.Time) { // not active continue } - if pass, err := spS.filterS.PassFiltersForEvent(ev.Tenant, - ev.Event, splPrfl.FilterIDs); err != nil { + if pass, err := spS.filterS.Pass(ev.Tenant, splPrfl.FilterIDs, + utils.NavigableMap(ev.Event)); err != nil { return nil, err } else if !pass { continue @@ -276,8 +276,8 @@ func (spS *SupplierService) sortedSuppliersForEvent(args *ArgsGetSuppliers) (sor var spls []*Supplier for _, s := range splPrfl.Suppliers { if len(s.FilterIDs) != 0 { // filters should be applied, check them here - if pass, err := spS.filterS.PassFiltersForEvent(args.Tenant, - args.Event, s.FilterIDs); err != nil { + if pass, err := spS.filterS.Pass(args.Tenant, s.FilterIDs, + utils.NavigableMap(args.Event)); err != nil { return nil, err } else if !pass { continue diff --git a/engine/thresholds.go b/engine/thresholds.go index 3051dbeb9..fe335a788 100644 --- a/engine/thresholds.go +++ b/engine/thresholds.go @@ -245,7 +245,8 @@ func (tS *ThresholdService) matchingThresholdsForEvent(args *ArgsProcessEvent) ( !tPrfl.ActivationInterval.IsActiveAtTime(*args.Time) { // not active continue } - if pass, err := tS.filterS.PassFiltersForEvent(args.Tenant, args.Event, tPrfl.FilterIDs); err != nil { + if pass, err := tS.filterS.Pass(args.Tenant, tPrfl.FilterIDs, + utils.NavigableMap(args.Event)); err != nil { return nil, err } else if !pass { continue diff --git a/utils/dataprovider.go b/utils/dataprovider.go new file mode 100644 index 000000000..040120fdd --- /dev/null +++ b/utils/dataprovider.go @@ -0,0 +1,26 @@ +/* +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 + +// DataProvider is a data source from multiple formats +type DataProvider interface { + FieldAsInterface(fldPath []string) (interface{}, error) + FieldAsString(fldPath []string) (string, error) + String() string // printable versin of data +} diff --git a/utils/navigablemap.go b/utils/navigablemap.go index 1884f0533..64b4f8021 100644 --- a/utils/navigablemap.go +++ b/utils/navigablemap.go @@ -21,7 +21,6 @@ package utils import ( "errors" "fmt" - "strings" ) // CGRReplier is the interface supported by replies convertible to CGRReply @@ -32,22 +31,21 @@ type NavigableMapper interface { // NavigableMap is a map who's values can be navigated via path type NavigableMap map[string]interface{} -// GetField returns the field value as interface{} for the path specified -func (nM NavigableMap) GetField(fldPath string, sep string) (fldVal interface{}, err error) { - path := strings.Split(fldPath, sep) - lenPath := len(path) +// FieldAsInterface returns the field value as interface{} for the path specified +// implements DataProvider +func (nM NavigableMap) FieldAsInterface(fldPath []string) (fldVal interface{}, err error) { + lenPath := len(fldPath) if lenPath == 0 { return nil, errors.New("empty field path") } lastMp := nM // last map when layered var canCast bool - for i, spath := range path { + for i, spath := range fldPath { if i == lenPath-1 { // lastElement var has bool fldVal, has = lastMp[spath] if !has { - err = fmt.Errorf("no field with path: <%s>", fldPath) - return + return nil, ErrNotFound } return } else { @@ -58,7 +56,8 @@ func (nM NavigableMap) GetField(fldPath string, sep string) (fldVal interface{}, } lastMp, canCast = elmnt.(map[string]interface{}) if !canCast { - err = fmt.Errorf("cannot cast field: %s to map[string]interface{}", ToJSON(elmnt)) + err = fmt.Errorf("cannot cast field: %s to map[string]interface{}", + ToJSON(elmnt)) return } } @@ -67,10 +66,11 @@ func (nM NavigableMap) GetField(fldPath string, sep string) (fldVal interface{}, return } -// GetFieldAsString returns the field value as string for the path specified -func (nM NavigableMap) GetFieldAsString(fldPath string, sep string) (fldVal string, err error) { +// FieldAsString returns the field value as string for the path specified +// implements DataProvider +func (nM NavigableMap) FieldAsString(fldPath []string) (fldVal string, err error) { var valIface interface{} - valIface, err = nM.GetField(fldPath, sep) + valIface, err = nM.FieldAsInterface(fldPath) if err != nil { return } @@ -81,6 +81,10 @@ func (nM NavigableMap) GetFieldAsString(fldPath string, sep string) (fldVal stri return } +func (nM NavigableMap) String() string { + return ToJSON(nM) +} + // NewCGRReply is specific to replies coming from CGRateS func NewCGRReply(rply NavigableMapper, errRply error) (nM map[string]interface{}, err error) { if errRply != nil { diff --git a/utils/navigablemap_test.go b/utils/navigablemap_test.go index 31172cd88..de1e53e08 100644 --- a/utils/navigablemap_test.go +++ b/utils/navigablemap_test.go @@ -20,6 +20,7 @@ package utils import ( "errors" "reflect" + "strings" "testing" ) @@ -35,19 +36,22 @@ func TestNavMapGetFieldAsString(t *testing.T) { "AnotherFirstLevel": "ValAnotherFirstLevel", } eVal := "Val1" - if strVal, err := nM.GetFieldAsString("FirstLevel>SecondLevel>ThirdLevel>Fld1", ">"); err != nil { + if strVal, err := nM.FieldAsString( + strings.Split("FirstLevel>SecondLevel>ThirdLevel>Fld1", ">")); err != nil { t.Error(err) } else if strVal != eVal { t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) } eVal = "ValAnotherFirstLevel" - if strVal, err := nM.GetFieldAsString("AnotherFirstLevel", ">"); err != nil { + if strVal, err := nM.FieldAsString( + strings.Split("AnotherFirstLevel", ">")); err != nil { t.Error(err) } else if strVal != eVal { t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) } fPath := "NonExisting>AnotherFirstLevel" - if _, err := nM.GetFieldAsString(fPath, ">"); err.Error() != errors.New("no map at path: ").Error() { + if _, err := nM.FieldAsString(strings.Split(fPath, ">")); err.Error() != + errors.New("no map at path: ").Error() { t.Error(err) } }