diff --git a/agents/agentreq.go b/agents/agentreq.go index a7ba1affd..731e4660e 100644 --- a/agents/agentreq.go +++ b/agents/agentreq.go @@ -303,6 +303,26 @@ func (ar *AgentRequest) ParseField( iFaceVals[i] = utils.StringToInterface(strVal) } out, err = utils.Difference(iFaceVals...) + case utils.MetaMultiply: + iFaceVals := make([]interface{}, len(cfgFld.Value)) + for i, val := range cfgFld.Value { + strVal, err := val.ParseDataProvider(ar, utils.NestingSep) + if err != nil { + return "", err + } + iFaceVals[i] = utils.StringToInterface(strVal) + } + out, err = utils.Multiply(iFaceVals...) + case utils.MetaDivide: + iFaceVals := make([]interface{}, len(cfgFld.Value)) + for i, val := range cfgFld.Value { + strVal, err := val.ParseDataProvider(ar, utils.NestingSep) + if err != nil { + return "", err + } + iFaceVals[i] = utils.StringToInterface(strVal) + } + out, err = utils.Divide(iFaceVals...) case utils.MetaValueExponent: if len(cfgFld.Value) != 2 { return nil, fmt.Errorf("invalid arguments <%s> to %s", diff --git a/agents/agentreq_test.go b/agents/agentreq_test.go index 8dc163ae9..80a84ce28 100644 --- a/agents/agentreq_test.go +++ b/agents/agentreq_test.go @@ -1381,6 +1381,94 @@ func TestAgReqParseFieldMetaDifference(t *testing.T) { } } +func TestAgReqParseFieldMetaMultiply(t *testing.T) { + //creater diameter message + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) + m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(2)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000004")), // Subscription-Id-Data + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(20000)), + }}) + //create diameterDataProvider + dP := newDADataProvider(nil, m) + cfg, _ := config.NewDefaultCGRConfig() + data := engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil) + filterS := engine.NewFilterS(cfg, nil, dm) + //pass the data provider to agent request + agReq := NewAgentRequest(dP, nil, nil, nil, nil, "cgrates.org", "", filterS, nil, nil) + + tplFlds := []*config.FCTemplate{ + &config.FCTemplate{Tag: "Multiply", Filters: []string{}, + Path: "Multiply", Type: utils.MetaMultiply, + Value: config.NewRSRParsersMustCompile("15;~*req.Session-Id", true, utils.INFIELD_SEP), + Mandatory: true}, + } + if _, err := agReq.ParseField(tplFlds[0]); err == nil || + err.Error() != `strconv.ParseInt: parsing "simuhuawei;1449573472;00002": invalid syntax` { + t.Error(err) + } + + tplFlds = []*config.FCTemplate{ + &config.FCTemplate{Tag: "Multiply", Filters: []string{}, + Path: "Multiply", Type: utils.MetaMultiply, + Value: config.NewRSRParsersMustCompile("15;15", true, utils.INFIELD_SEP), + Mandatory: true}, + } + expected := int64(225) + if out, err := agReq.ParseField(tplFlds[0]); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(out, expected) { + t.Errorf("expecting: <%+v>, %T received: <%+v> %T", expected, expected, out, out) + } +} + +func TestAgReqParseFieldMetaDivide(t *testing.T) { + //creater diameter message + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) + m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(2)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000004")), // Subscription-Id-Data + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(20000)), + }}) + //create diameterDataProvider + dP := newDADataProvider(nil, m) + cfg, _ := config.NewDefaultCGRConfig() + data := engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil) + filterS := engine.NewFilterS(cfg, nil, dm) + //pass the data provider to agent request + agReq := NewAgentRequest(dP, nil, nil, nil, nil, "cgrates.org", "", filterS, nil, nil) + + tplFlds := []*config.FCTemplate{ + &config.FCTemplate{Tag: "Divide", Filters: []string{}, + Path: "Divide", Type: utils.MetaDivide, + Value: config.NewRSRParsersMustCompile("15;~*req.Session-Id", true, utils.INFIELD_SEP), + Mandatory: true}, + } + if _, err := agReq.ParseField(tplFlds[0]); err == nil || + err.Error() != `strconv.ParseInt: parsing "simuhuawei;1449573472;00002": invalid syntax` { + t.Error(err) + } + + tplFlds = []*config.FCTemplate{ + &config.FCTemplate{Tag: "Divide", Filters: []string{}, + Path: "Divide", Type: utils.MetaDivide, + Value: config.NewRSRParsersMustCompile("15;3", true, utils.INFIELD_SEP), + Mandatory: true}, + } + expected := int64(5) + if out, err := agReq.ParseField(tplFlds[0]); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(out, expected) { + t.Errorf("expecting: <%+v>, %T received: <%+v> %T", expected, expected, out, out) + } +} + func TestAgReqParseFieldMetaValueExponent(t *testing.T) { //creater diameter message m := diam.NewRequest(diam.CreditControl, 4, nil) diff --git a/engine/attributes.go b/engine/attributes.go index 393deb7ed..d72973a6f 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -223,6 +223,48 @@ func (alS *AttributeService) processEvent(args *AttrArgsProcessEvent) ( return nil, err } substitute = utils.IfaceAsString(ifaceSum) + case utils.MetaDifference: + iFaceVals := make([]interface{}, len(attribute.Value)) + for i, val := range attribute.Value { + strVal, err := val.ParseDataProvider(evNm, utils.NestingSep) + if err != nil { + return nil, err + } + iFaceVals[i] = utils.StringToInterface(strVal) + } + ifaceSum, err := utils.Difference(iFaceVals...) + if err != nil { + return nil, err + } + substitute = utils.IfaceAsString(ifaceSum) + case utils.MetaMultiply: + iFaceVals := make([]interface{}, len(attribute.Value)) + for i, val := range attribute.Value { + strVal, err := val.ParseDataProvider(evNm, utils.NestingSep) + if err != nil { + return nil, err + } + iFaceVals[i] = utils.StringToInterface(strVal) + } + ifaceSum, err := utils.Multiply(iFaceVals...) + if err != nil { + return nil, err + } + substitute = utils.IfaceAsString(ifaceSum) + case utils.MetaDivide: + iFaceVals := make([]interface{}, len(attribute.Value)) + for i, val := range attribute.Value { + strVal, err := val.ParseDataProvider(evNm, utils.NestingSep) + if err != nil { + return nil, err + } + iFaceVals[i] = utils.StringToInterface(strVal) + } + ifaceSum, err := utils.Divide(iFaceVals...) + if err != nil { + return nil, err + } + substitute = utils.IfaceAsString(ifaceSum) case utils.MetaValueExponent: if len(attribute.Value) != 2 { return nil, fmt.Errorf("invalid arguments <%s> to %s", @@ -247,6 +289,16 @@ func (alS *AttributeService) processEvent(args *AttrArgsProcessEvent) ( } substitute = strconv.FormatFloat(utils.Round(val*math.Pow10(exp), config.CgrConfig().GeneralCfg().RoundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64) + case utils.MetaUnixTimestamp: + val, err := attribute.Value.ParseDataProvider(evNm, utils.NestingSep) + if err != nil { + return nil, err + } + t, err := utils.ParseTimeDetectLayout(val, alS.cgrcfg.GeneralCfg().DefaultTimezone) + if err != nil { + return nil, err + } + substitute = strconv.Itoa(int(t.Unix())) default: // backwards compatible in case that Type is empty substitute, err = attribute.Value.ParseDataProvider(evNm, utils.NestingSep) } diff --git a/utils/reflect.go b/utils/reflect.go index db2dd8e07..72e891170 100644 --- a/utils/reflect.go +++ b/utils/reflect.go @@ -627,6 +627,98 @@ func Difference(items ...interface{}) (diff interface{}, err error) { return } +// Multiply attempts to multiply multiple items +// returns the result or error if not comparable +func Multiply(items ...interface{}) (mlt interface{}, err error) { + //we need at least 2 items to diff them + if len(items) < 2 { + return nil, ErrNotEnoughParameters + } + switch dt := items[0].(type) { + case float64: + mlt = dt + for _, item := range items[1:] { + if itmVal, err := IfaceAsFloat64(item); err != nil { + return nil, err + } else { + mlt = mlt.(float64) * itmVal + } + } + case int64: + mlt = dt + for _, item := range items[1:] { + if itmVal, err := IfaceAsInt64(item); err != nil { + return nil, err + } else { + mlt = mlt.(int64) * itmVal + } + } + case int: + // need explicit conversion for int + if firstItmVal, err := IfaceAsInt64(dt); err != nil { + return nil, err + } else { + mlt = firstItmVal + } + for _, item := range items[1:] { + if itmVal, err := IfaceAsInt64(item); err != nil { + return nil, err + } else { + mlt = mlt.(int64) * itmVal + } + } + default: // unsupported comparison + return nil, fmt.Errorf("unsupported type") + } + return +} + +// Divide attempts to divide multiple items +// returns the result or error if not comparable +func Divide(items ...interface{}) (mlt interface{}, err error) { + //we need at least 2 items to diff them + if len(items) < 2 { + return nil, ErrNotEnoughParameters + } + switch dt := items[0].(type) { + case float64: + mlt = dt + for _, item := range items[1:] { + if itmVal, err := IfaceAsFloat64(item); err != nil { + return nil, err + } else { + mlt = mlt.(float64) / itmVal + } + } + case int64: + mlt = dt + for _, item := range items[1:] { + if itmVal, err := IfaceAsInt64(item); err != nil { + return nil, err + } else { + mlt = mlt.(int64) / itmVal + } + } + case int: + // need explicit conversion for int + if firstItmVal, err := IfaceAsInt64(dt); err != nil { + return nil, err + } else { + mlt = firstItmVal + } + for _, item := range items[1:] { + if itmVal, err := IfaceAsInt64(item); err != nil { + return nil, err + } else { + mlt = mlt.(int64) / itmVal + } + } + default: // unsupported comparison + return nil, fmt.Errorf("unsupported type") + } + return +} + // ReflectFieldMethodInterface parses intf attepting to return the field value or error otherwise // Supports "ExtraFields" where additional fields are dynamically inserted in map with field name: extraFieldsLabel func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interface{}, err error) { diff --git a/utils/reflect_test.go b/utils/reflect_test.go index 4b4564682..5b4024578 100644 --- a/utils/reflect_test.go +++ b/utils/reflect_test.go @@ -641,6 +641,56 @@ func TestDifference(t *testing.T) { } +func TestMultiply(t *testing.T) { + if _, err := Multiply(10); err == nil || err != ErrNotEnoughParameters { + t.Error(err) + } + if _, err := Multiply(10, 1.2, false); err == nil || err.Error() != "cannot convert field: 1.2 to int" { + t.Error(err) + } + if diff, err := Multiply(12, 1, 2, 3); err != nil { + t.Error(err) + } else if diff != int64(72) { + t.Errorf("Expecting: 72, received: %+v", diff) + } + if diff, err := Multiply(8.0, 4.0, 2.0, 1.0); err != nil { + t.Error(err) + } else if diff != 64.0 { + t.Errorf("Expecting: 64.0, received: %+v", diff) + } + + if diff, err := Multiply(8.0, 4, 6.0, 1.0); err != nil { + t.Error(err) + } else if diff != 192.0 { + t.Errorf("Expecting: 192.0, received: %+v", diff) + } +} + +func TestDivide(t *testing.T) { + if _, err := Divide(10); err == nil || err != ErrNotEnoughParameters { + t.Error(err) + } + if _, err := Divide(10, 1.2, false); err == nil || err.Error() != "cannot convert field: 1.2 to int" { + t.Error(err) + } + if diff, err := Divide(12, 1, 2, 3); err != nil { + t.Error(err) + } else if diff != int64(2) { + t.Errorf("Expecting: 2, received: %+v", diff) + } + if diff, err := Divide(8.0, 4.0, 2.0, 1.0); err != nil { + t.Error(err) + } else if diff != 1.0 { + t.Errorf("Expecting: 1.0, received: %+v", diff) + } + + if diff, err := Divide(8.0, 4, 6.0, 1.0); err != nil { + t.Error(err) + } else if diff != 0.3333333333333333 { + t.Errorf("Expecting: 0.3333333333333333, received: %+v", diff) + } +} + func TestEqualTo(t *testing.T) { if gte, err := EqualTo(1, 1.2); err != nil { t.Error(err)