diff --git a/apier/v1/attributes.go b/apier/v1/attributes.go index aa7c2d399..007699fb0 100644 --- a/apier/v1/attributes.go +++ b/apier/v1/attributes.go @@ -90,6 +90,6 @@ func (alSv1 *AttributeSv1) GetAttributeForEvent(ev *utils.CGREvent, // ProcessEvent will replace event fields with the ones in maching AttributeProfile func (alSv1 *AttributeSv1) ProcessEvent(ev *utils.CGREvent, - reply *string) error { + reply *engine.AttrSProcessEventReply) error { return alSv1.attrS.V1ProcessEvent(ev, reply) } diff --git a/apier/v1/attributes_it_test.go b/apier/v1/attributes_it_test.go index 73de1a55e..0682e7e2e 100644 --- a/apier/v1/attributes_it_test.go +++ b/apier/v1/attributes_it_test.go @@ -51,6 +51,7 @@ var sTestsAlsPrf = []func(t *testing.T){ testAttributeSRPCConn, testAttributeSLoadFromFolder, testAttributeSGetAttributeForEvent, + testAttributeSProcessEvent, testAttributeSGetAlsPrfBeforeSet, testAttributeSSetAlsPrf, testAttributeSUpdateAlsPrf, @@ -144,7 +145,7 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { "Destination": "+491511231234", }, } - eAttrPrf := engine.ExternalAttributeProfile{ + eAttrPrf := &engine.ExternalAttributeProfile{ Tenant: ev.Tenant, ID: "ATTR_1", FilterIDs: []string{"FLTR_ACNT_1007"}, @@ -156,18 +157,18 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { FieldName: utils.ACCOUNT, Initial: utils.ANY, Alias: "1001", - Append: true, + Append: false, }, &engine.Attribute{ FieldName: utils.SUBJECT, Initial: utils.ANY, Alias: "1001", - Append: false, + Append: true, }, }, Weight: 10.0, } - eAttrPrf2 := engine.ExternalAttributeProfile{ + eAttrPrf2 := &engine.ExternalAttributeProfile{ Tenant: ev.Tenant, ID: "ATTR_1", FilterIDs: []string{"FLTR_ACNT_1007"}, @@ -179,13 +180,13 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { FieldName: utils.SUBJECT, Initial: utils.ANY, Alias: "1001", - Append: false, + Append: true, }, &engine.Attribute{ FieldName: utils.ACCOUNT, Initial: utils.ANY, Alias: "1001", - Append: true, + Append: false, }, }, Weight: 10.0, @@ -194,13 +195,62 @@ func testAttributeSGetAttributeForEvent(t *testing.T) { if err := attrSRPC.Call(utils.AttributeSv1GetAttributeForEvent, ev, &attrReply); err != nil { t.Error(err) - } else if !reflect.DeepEqual(eAttrPrf, attrReply) && - !reflect.DeepEqual(eAttrPrf2, attrReply) { // second for reversed order of attributes + } else if !reflect.DeepEqual(eAttrPrf, &attrReply) && + !reflect.DeepEqual(eAttrPrf2, &attrReply) { // second for reversed order of attributes t.Errorf("Expecting: %s, received: %s", utils.ToJSON(eAttrPrf), utils.ToJSON(attrReply)) } } +func testAttributeSProcessEvent(t *testing.T) { + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "testAttributeSProcessEvent", + Context: utils.StringPointer(utils.ALIAS_CONTEXT_RATING), + Event: map[string]interface{}{ + "Account": "1007", + "Destination": "+491511231234", + }, + } + eRply := &engine.AttrSProcessEventReply{ + MatchedProfile: "ATTR_1", + AlteredFields: []string{"Subject", "Account"}, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "testAttributeSProcessEvent", + Context: utils.StringPointer(utils.ALIAS_CONTEXT_RATING), + Event: map[string]interface{}{ + "Account": "1001", + "Subject": "1001", + "Destination": "+491511231234", + }, + }, + } + eRply2 := &engine.AttrSProcessEventReply{ + MatchedProfile: "ATTR_1", + AlteredFields: []string{"Account", "Subject"}, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "testAttributeSProcessEvent", + Context: utils.StringPointer(utils.ALIAS_CONTEXT_RATING), + Event: map[string]interface{}{ + "Account": "1001", + "Subject": "1001", + "Destination": "+491511231234", + }, + }, + } + var rplyEv engine.AttrSProcessEventReply + if err := attrSRPC.Call(utils.AttributeSv1ProcessEvent, + ev, &rplyEv); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eRply, &rplyEv) && + !reflect.DeepEqual(eRply2, &rplyEv) { // second for reversed order of attributes + t.Errorf("Expecting: %s, received: %s", + utils.ToJSON(eRply), utils.ToJSON(rplyEv)) + } +} + func testAttributeSSetAlsPrf(t *testing.T) { alsPrf = &engine.ExternalAttributeProfile{ Tenant: "cgrates.org", diff --git a/data/tariffplans/tutorial/Attributes.csv b/data/tariffplans/tutorial/Attributes.csv index 3be302b3d..95b10f76e 100644 --- a/data/tariffplans/tutorial/Attributes.csv +++ b/data/tariffplans/tutorial/Attributes.csv @@ -1,3 +1,3 @@ #Tenant,ID,FilterIDs,ActivationInterval,Context,FieldName,Initial,Alias,Append,Weight -cgrates.org,ATTR_1,FLTR_ACNT_1007,2014-01-14T00:00:00Z,*rating,Account,*any,1001,true,10 -cgrates.org,ATTR_1,,,,Subject,*any,1001,false, +cgrates.org,ATTR_1,FLTR_ACNT_1007,2014-01-14T00:00:00Z,*rating,Account,*any,1001,false,10 +cgrates.org,ATTR_1,,,,Subject,*any,1001,true, diff --git a/engine/attribbutes.go b/engine/attributes.go similarity index 65% rename from engine/attribbutes.go rename to engine/attributes.go index e5b2d64fc..8a344e69c 100644 --- a/engine/attribbutes.go +++ b/engine/attributes.go @@ -98,31 +98,80 @@ func (alS *AttributeService) matchingAttributeProfilesForEvent(ev *utils.CGREven return } -func (alS *AttributeService) attributeProfileForEvent(ev *utils.CGREvent) (alsPrfl *AttributeProfile, err error) { - var alsPrfls AttributeProfiles - if alsPrfls, err = alS.matchingAttributeProfilesForEvent(ev); err != nil { +func (alS *AttributeService) attributeProfileForEvent(ev *utils.CGREvent) (attrPrfl *AttributeProfile, err error) { + var attrPrfls AttributeProfiles + if attrPrfls, err = alS.matchingAttributeProfilesForEvent(ev); err != nil { return - } else if len(alsPrfls) == 0 { + } else if len(attrPrfls) == 0 { return nil, utils.ErrNotFound } - return alsPrfls[0], nil + return attrPrfls[0], nil +} + +type AttrSProcessEventReply struct { + MatchedProfile string + AlteredFields []string + CGREvent *utils.CGREvent +} + +// processEvent will match event with attribute profile and do the necessary replacements +func (alS *AttributeService) processEvent(ev *utils.CGREvent) (rply *AttrSProcessEventReply, err error) { + attrPrf, err := alS.attributeProfileForEvent(ev) + if err != nil { + return nil, err + } + rply = &AttrSProcessEventReply{MatchedProfile: attrPrf.ID, CGREvent: ev.Clone()} + for fldName, intialMp := range attrPrf.Attributes { + initEvValIf, has := ev.Event[fldName] + if !has { // we don't have initial in event, try append + if anyInitial, has := intialMp[utils.ANY]; has && anyInitial.Append { + rply.CGREvent.Event[fldName] = anyInitial.Alias + rply.AlteredFields = append(rply.AlteredFields, fldName) + } + continue + } + initEvVal, cast := utils.CastFieldIfToString(initEvValIf) + if !cast { + utils.Logger.Warning( + fmt.Sprintf("<%s> ev: %s, cannot cast field: %s to string", + utils.AttributeS, ev, fldName)) + continue + } + attrVal, has := intialMp[initEvVal] + if !has { + attrVal, has = intialMp[utils.ANY] + } + if has { + rply.CGREvent.Event[fldName] = attrVal.Alias + rply.AlteredFields = append(rply.AlteredFields, fldName) + } + } + return } func (alS *AttributeService) V1GetAttributeForEvent(ev *utils.CGREvent, - extAlsPrf *ExternalAttributeProfile) (err error) { - alsPrf, err := alS.attributeProfileForEvent(ev) + extattrPrf *ExternalAttributeProfile) (err error) { + attrPrf, err := alS.attributeProfileForEvent(ev) if err != nil { if err != utils.ErrNotFound { err = utils.NewErrServerError(err) } return err } - eAlsPrfl := NewExternalAttributeProfileFromAttributeProfile(alsPrf) - *extAlsPrf = *eAlsPrfl + eattrPrfl := NewExternalAttributeProfileFromAttributeProfile(attrPrf) + *extattrPrf = *eattrPrfl return } func (alS *AttributeService) V1ProcessEvent(ev *utils.CGREvent, - reply *string) (err error) { + reply *AttrSProcessEventReply) (err error) { + evReply, err := alS.processEvent(ev) + if err != nil { + if err != utils.ErrNotFound { + err = utils.NewErrServerError(err) + } + return err + } + *reply = *evReply return } diff --git a/utils/cgrevent.go b/utils/cgrevent.go index fb4bab0d5..3e44d6267 100644 --- a/utils/cgrevent.go +++ b/utils/cgrevent.go @@ -29,7 +29,7 @@ import ( type CGREvent struct { Tenant string ID string - Context string // attach the event to a context + Context *string // attach the event to a context Time *time.Time // event time Event map[string]interface{} } @@ -142,3 +142,21 @@ func (ev *CGREvent) FilterableEvent(fltredFields []string) (fEv map[string]inter } return } + +func (ev *CGREvent) Clone() (clned *CGREvent) { + clned = &CGREvent{ + Tenant: ev.Tenant, + ID: ev.ID, + Event: make(map[string]interface{}), // a bit forced but safe + } + if ev.Context != nil { + clned.Context = StringPointer(*ev.Context) + } + if ev.Time != nil { + clned.Time = TimePointer(*ev.Time) + } + for k, v := range ev.Event { + clned.Event[k] = v + } + return +} diff --git a/utils/consts.go b/utils/consts.go index 1078cff18..a9042f2c7 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -630,6 +630,7 @@ const ( // AliasS APIs const ( AttributeSv1GetAttributeForEvent = "AttributeSv1.GetAttributeForEvent" + AttributeSv1ProcessEvent = "AttributeSv1.ProcessEvent" ) func buildCacheInstRevPrefixes() {