diff --git a/config/rsrparser.go b/config/rsrparser.go index 4ba1c7d91..0611ca480 100644 --- a/config/rsrparser.go +++ b/config/rsrparser.go @@ -167,6 +167,22 @@ func (prsrs RSRParsers) ParseDataProviderWithInterfaces(dP utils.DataProvider) ( return } +// ParseDataProviderWithInterfaces will parse the dataprovider using DPDynamicInterface +func (prsrs RSRParsers) ParseDataProviderWithInterfaces2(dP utils.DataProvider) (out interface{}, err error) { + for i, prsr := range prsrs { + outPrsr, err := prsr.ParseDataProviderWithInterfaces2(dP) + if err != nil { + return nil, err + } + if i == 0 { + out = outPrsr + } else { + out = utils.IfaceAsString(out) + utils.IfaceAsString(outPrsr) + } + } + return +} + // GetIfaceFromValues returns an interface for each RSRParser func (prsrs RSRParsers) GetIfaceFromValues(evNm utils.DataProvider) (iFaceVals []interface{}, err error) { iFaceVals = make([]interface{}, len(prsrs)) @@ -311,6 +327,14 @@ func (prsr *RSRParser) parseValue(value string) (out string, err error) { return prsr.converters.ConvertString(value) } +// parseValue the field value from a string +func (prsr *RSRParser) parseValueInterface(value interface{}) (out interface{}, err error) { + for _, rsRule := range prsr.rsrRules { + value = rsRule.Process(utils.IfaceAsString(value)) + } + return prsr.converters.ConvertInterface(value) +} + // ParseValue will parse the value out considering converters func (prsr *RSRParser) ParseValue(value interface{}) (out string, err error) { out = prsr.path @@ -361,6 +385,26 @@ func (prsr *RSRParser) ParseDataProviderWithInterfaces(dP utils.DataProvider) (o return prsr.parseValue(utils.IfaceAsString(outIface)) } +// ParseDataProviderWithInterfaces will parse the dataprovider using DPDynamicInterface +func (prsr *RSRParser) ParseDataProviderWithInterfaces2(dP utils.DataProvider) (out interface{}, err error) { + if prsr.dynRules != nil { + var dynPath string + if dynPath, err = prsr.dynRules.ParseDataProvider(dP); err != nil { + return + } + var dynRSR *RSRParser + if dynRSR, err = NewRSRParser(prsr.Rules[:prsr.dynIdxStart] + dynPath + prsr.Rules[prsr.dynIdxEnd:]); err != nil { + return + } + return dynRSR.ParseDataProviderWithInterfaces2(dP) + } + var outIface interface{} + if outIface, err = utils.DPDynamicInterface(prsr.path, dP); err != nil { + return + } + return prsr.parseValueInterface(outIface) +} + // CompileDynRule will return the compiled dynamic rule func (prsr *RSRParser) CompileDynRule(dP utils.DataProvider) (p string, err error) { if prsr.dynRules == nil { diff --git a/engine/attributes.go b/engine/attributes.go index 843656954..3917505a7 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -335,6 +335,8 @@ func ParseAttribute(dp utils.DataProvider, attrType, path string, value config.R out, err = value.ParseValue(utils.EmptyString) case utils.MetaVariable, utils.MetaComposed: out, err = value.ParseDataProvider(dp) + case utils.MetaGeneric: + out, err = value.ParseDataProviderWithInterfaces2(dp) case utils.MetaUsageDifference: if len(value) != 2 { return "", fmt.Errorf("invalid arguments <%s> to %s", diff --git a/engine/routes.go b/engine/routes.go index 481d39b2a..3e70dbc80 100644 --- a/engine/routes.go +++ b/engine/routes.go @@ -474,11 +474,16 @@ func (rp *RouteProfile) Set(path []string, val interface{}, newBranch bool, _ st valA, err = utils.IfaceAsStringSlice(val) rp.SortingParameters = append(rp.SortingParameters, valA...) case utils.Sorting: - rp.Sorting = utils.IfaceAsString(val) + if valStr := utils.IfaceAsString(val); len(valStr) != 0 { + rp.Sorting = valStr + } case utils.Weights: rp.Weights, err = utils.NewDynamicWeightsFromString(utils.IfaceAsString(val), utils.InfieldSep, utils.ANDSep) } case 2: + if val == utils.EmptyString { + return + } if path[0] != utils.Routes { return utils.ErrWrongPath } diff --git a/general_tests/routes_cases_it_test.go b/general_tests/routes_cases_it_test.go index 4a63241bf..dd0b5c70c 100644 --- a/general_tests/routes_cases_it_test.go +++ b/general_tests/routes_cases_it_test.go @@ -177,6 +177,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { Weights: ";35", }, }, + Weights: ";0", } var reply *engine.APIRouteProfile if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, @@ -223,6 +224,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { Weights: ";30", }, }, + Weights: ";0", } if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, &utils.TenantID{ID: "ROUTE_ACNT_1002", Tenant: "cgrates.org"}, @@ -263,6 +265,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { Weights: ";20", }, }, + Weights: ";0", } if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, &utils.TenantID{ID: "ROUTE_ACNT_1003", Tenant: "cgrates.org"}, @@ -303,6 +306,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { Weights: ";10", }, }, + Weights: ";0", } if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, &utils.TenantID{ID: "ROUTE_ACNT_1004", Tenant: "cgrates.org"}, @@ -339,6 +343,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { StatIDs: []string{"STATS_VENDOR_2:*distinct#~*req.Usage"}, }, }, + Weights: ";0", } if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, &utils.TenantID{ID: "ROUTE_ACNT_1005", Tenant: "cgrates.org"}, @@ -383,6 +388,7 @@ func testV1RtsCaseGetRoutesAfterLoading(t *testing.T) { Weights: ";10", }, }, + Weights: ";0", } if err := rtsCaseSv1BiRpc.Call(context.Background(), utils.AdminSv1GetRouteProfile, &utils.TenantID{ID: "ROUTE_HC1", Tenant: "cgrates.org"}, diff --git a/general_tests/session_volume_discount_it_test.go b/general_tests/session_volume_discount_it_test.go index bcd61b643..541317389 100644 --- a/general_tests/session_volume_discount_it_test.go +++ b/general_tests/session_volume_discount_it_test.go @@ -400,9 +400,10 @@ func testSessVolDiscProcessCDRCustomer(t *testing.T) { func testSessVolDiscAccountAfterDebiting(t *testing.T) { expectedAcc := utils.Account{ - Tenant: "cgrates.org", - ID: "ACNT_VOL1", - FilterIDs: []string{}, + Tenant: "cgrates.org", + ID: "ACNT_VOL1", + Opts: make(map[string]interface{}), + Weights: utils.DynamicWeights{{}}, Balances: map[string]*utils.Balance{ "ABS_VOLUME1": { ID: "ABS_VOLUME1", @@ -412,6 +413,7 @@ func testSessVolDiscAccountAfterDebiting(t *testing.T) { Weight: 30, }, }, + Opts: make(map[string]interface{}), Type: "*abstract", Units: utils.SumDecimal(&utils.Decimal{utils.NewDecimal(0, 0).Neg(utils.NewDecimal(1, 0).Big)}, utils.NewDecimal(1, 0)), // this should be -0 CostIncrements: []*utils.CostIncrement{ @@ -429,6 +431,7 @@ func testSessVolDiscAccountAfterDebiting(t *testing.T) { Weight: 20, }, }, + Opts: make(map[string]interface{}), Type: "*abstract", Units: utils.SumDecimal(&utils.Decimal{utils.NewDecimal(0, 0).Neg(utils.NewDecimal(1, 0).Big)}, utils.NewDecimal(1, 0)), CostIncrements: []*utils.CostIncrement{ @@ -459,7 +462,6 @@ func testSessVolDiscAccountAfterDebiting(t *testing.T) { RateProfileIDs: []string{"RP_SUPPLIER1"}, }, }, - ThresholdIDs: []string{}, } var result utils.Account if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1GetAccount, diff --git a/loaders/libloader.go b/loaders/libloader.go index 4a764d920..c2cabb38c 100644 --- a/loaders/libloader.go +++ b/loaders/libloader.go @@ -180,6 +180,8 @@ func (ar *record) SetFields(ctx *context.Context, tmpls []*config.FCTemplate, fi err = ar.Compose(fullPath, nMItm) case utils.MetaGroup: // in case of *group type simply append to valSet err = ar.Append(fullPath, nMItm) + case utils.MetaGeneric: + err = ar.Set(fullPath, out) default: err = ar.SetAsSlice(fullPath, nMItm) } @@ -293,3 +295,17 @@ func prepareData(prf profile, lData []*utils.OrderedNavigableMap, rsrSep string) } return } + +// Set implements utils.NMInterface +func (ar *record) Set(fullPath *utils.FullPath, nm interface{}) (err error) { + switch fullPath.PathSlice[0] { + default: + return ar.data.Set(fullPath, nm) + case utils.MetaTmp: + _, err = ar.tmp.Set(fullPath.PathSlice[1:], nm) + return + case utils.MetaUCH: + ar.cache.Set(fullPath.Path[5:], nm, nil) + return + } +} diff --git a/utils/dataconverter.go b/utils/dataconverter.go index de91a2ce8..d80719115 100644 --- a/utils/dataconverter.go +++ b/utils/dataconverter.go @@ -49,6 +49,17 @@ func (dcs DataConverters) ConvertString(in string) (out string, err error) { return IfaceAsString(outIface), nil } +// ConvertString converts from and to string +func (dcs DataConverters) ConvertInterface(in interface{}) (out interface{}, err error) { + out = in + for _, cnv := range dcs { + if out, err = cnv.Convert(out); err != nil { + return + } + } + return +} + // DataConverter represents functions which should convert input into output type DataConverter interface { Convert(interface{}) (interface{}, error) @@ -474,6 +485,26 @@ type LengthConverter struct{} func (LengthConverter) Convert(in interface{}) (out interface{}, err error) { switch val := in.(type) { case string: + if len(val) > 2 { + var tmp interface{} + var l func() (interface{}, error) + switch { + case val[0] == '[' && val[len(val)-1] == ']': + v := []interface{}{} + l = func() (interface{}, error) { return len(v), nil } + tmp = &v + + case val[0] == '{' && val[len(val)-1] == '}': + v := map[string]interface{}{} + l = func() (interface{}, error) { return len(v), nil } + tmp = &v + } + if tmp != nil { + if err := json.Unmarshal([]byte(val), tmp); err == nil { + return l() + } + } + } return len(val), nil case []string: return len(val), nil diff --git a/utils/decimal.go b/utils/decimal.go index 9a2f849e6..a0c3afe4e 100644 --- a/utils/decimal.go +++ b/utils/decimal.go @@ -229,11 +229,9 @@ func (d *Decimal) Compare(d2 *Decimal) int { // NewDecimalFromString converts a string to decimal func NewDecimalFromString(value string) (*Decimal, error) { - z, ok := decimal.WithContext(DecimalContext).SetString(value) - // verify ok and check if the value was converted successfuly - // and the big is a valid number - if !ok || z.IsNaN(0) { - return nil, fmt.Errorf("can't convert <%+v> to decimal", value) + z, err := StringAsBig(value) + if err != nil { + return nil, err } return &Decimal{z}, nil }