/* 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package engine import ( "errors" "slices" "time" "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "github.com/ericlagergren/decimal" ) // GetFloat64Opts checks the specified option names in order among the keys in APIOpts returning the first value it finds as float64, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetFloat64Opts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicFloat64Opt, optNames ...string) (cfgOpt float64, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsFloat64(opt) } else if !errors.Is(err, utils.ErrNotFound) { return 0, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return 0, err } else if pass { return opt.Value(dP) } } return } // GetDurationOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as time.Duration, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetDurationOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicDurationOpt, optNames ...string) (cfgOpt time.Duration, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsDuration(opt) } else if !errors.Is(err, utils.ErrNotFound) { return 0, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return 0, err } else if pass { return opt.Value(dP) } } return } // GetDurationPointerOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as *time.Duration, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetDurationPointerOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicDurationPointerOpt, optNames ...string) (cfgOpt *time.Duration, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { var value time.Duration value, err = utils.IfaceAsDuration(opt) if err != nil { return nil, err } return utils.DurationPointer(value), nil } else if !errors.Is(err, utils.ErrNotFound) { return nil, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return nil, err } else if pass { return opt.Value(dP) } } return } // GetStringOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as string, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetStringOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicStringOpt, optNames ...string) (cfgOpt string, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsString(opt), nil } else if !errors.Is(err, utils.ErrNotFound) { return "", err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return utils.EmptyString, err } else if pass { return opt.Value(dP) } } return } // GetTimeOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as time.Time, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetTimeOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicStringOpt, tmz string, optNames ...string) (_ time.Time, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsTime(opt, tmz) } else if !errors.Is(err, utils.ErrNotFound) { return time.Time{}, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } var pass bool if pass, err = fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return } else if pass { var dur string dur, err = opt.Value(dP) if err != nil { return } return utils.ParseTimeDetectLayout(dur, tmz) } } return } // GetStringSliceOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as []string, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetStringSliceOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicStringSliceOpt, dftOpt []string, optNames ...string) (cfgOpt []string, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsStringSlice(opt) } else if !errors.Is(err, utils.ErrNotFound) { return nil, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return nil, err } else if pass { return opt.Values, nil } } return dftOpt, nil // return the default value if there are no options and none of the filters pass } // GetIntOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as int, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetIntOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicIntOpt, optNames ...string) (cfgOpt int, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsInt(opt) } else if !errors.Is(err, utils.ErrNotFound) { return 0, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return 0, err } else if pass { return opt.Value(dP) } } return } // GetBoolOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as bool, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetBoolOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicBoolOpt, optNames ...string) (cfgOpt bool, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsBool(opt) } else if !errors.Is(err, utils.ErrNotFound) { return false, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return false, err } else if pass { return opt.Value(dP) } } return } // GetDecimalBigOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as *decimal.Big, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetDecimalBigOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicDecimalOpt, optNames ...string) (cfgOpt *decimal.Big, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return utils.IfaceAsBig(opt) } else if !errors.Is(err, utils.ErrNotFound) { return nil, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return nil, err } else if pass { return opt.Value(dP) } } return } // GetInterfaceOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as any, otherwise it // returns the config option if at least one filter passes or the default value if none of them do func GetInterfaceOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicInterfaceOpt, optNames ...string) (cfgOpt any, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { return opt, nil } else if !errors.Is(err, utils.ErrNotFound) { return false, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return false, err } else if pass { return opt.Value, nil } } return } // GetIntPointerOpts checks the specified option names in order among the keys in APIOpts returning the first value it finds as *int, otherwise it // returns the config option if at least one filter passes or NOT_FOUND if none of them do func GetIntPointerOpts(ctx *context.Context, tnt string, dP utils.DataProvider, cch map[string]any, fS *FilterS, dynOpts []*config.DynamicIntPointerOpt, optNames ...string) (cfgOpt *int, err error) { if opt, err := optIfaceFromDP(dP, cch, optNames); err == nil { var value int if value, err = utils.IfaceAsInt(opt); err != nil { return nil, err } return utils.IntPointer(value), nil } else if !errors.Is(err, utils.ErrNotFound) { return nil, err } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, dP); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return nil, err } else if pass { return opt.Value(dP) } } return nil, nil } // GetDurationPointerOptsFromMultipleMaps checks the specified option names in order among the keys in APIOpts, then in startOpts, returning the first value it finds as *time.Duration, // otherwise it returns the config option if at least one filter passes or NOT_FOUND if none of them do func GetDurationPointerOptsFromMultipleMaps(ctx *context.Context, tnt string, eventStart, apiOpts, startOpts map[string]any, fS *FilterS, dynOpts []*config.DynamicDurationPointerOpt, optName string) (cfgOpt *time.Duration, err error) { var value time.Duration if opt, has := apiOpts[optName]; has { if value, err = utils.IfaceAsDuration(opt); err != nil { return nil, err } return utils.DurationPointer(value), nil } else if opt, has = startOpts[optName]; has { if value, err = utils.IfaceAsDuration(opt); err != nil { return nil, err } return utils.DurationPointer(value), nil } evMS := utils.MapStorage{ utils.MetaOpts: apiOpts, utils.MetaReq: eventStart, } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, evMS); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return nil, err } else if pass { return opt.Value(evMS) } } return nil, nil } // GetDurationOptsFromMultipleMaps checks the specified option names in order among the keys in APIOpts, then in startOpts, returning the first value it finds as time.Duration, // otherwise it returns the config option if at least one filter passes or the default one if none of them do func GetDurationOptsFromMultipleMaps(ctx *context.Context, tnt string, eventStart, apiOpts, startOpts map[string]any, fS *FilterS, dynOpts []*config.DynamicDurationOpt, dftOpt time.Duration, optName string) (cfgOpt time.Duration, err error) { var value time.Duration if opt, has := apiOpts[optName]; has { if value, err = utils.IfaceAsDuration(opt); err != nil { return 0, err } return value, nil } else if opt, has = startOpts[optName]; has { if value, err = utils.IfaceAsDuration(opt); err != nil { return 0, err } return value, nil } evMS := utils.MapStorage{ utils.MetaOpts: apiOpts, utils.MetaReq: eventStart, } for _, opt := range dynOpts { // iterate through the options if !slices.Contains([]string{utils.EmptyString, utils.MetaAny, tnt}, opt.Tenant) { continue } if pass, err := fS.Pass(ctx, tnt, opt.FilterIDs, evMS); err != nil { // check if the filter is passing for the DataProvider and return the option if it does return 0, err } else if pass { return opt.Value(evMS) } } return dftOpt, nil // return the default value if there are no options and none of the filters pass } func ConvertOptsToMapStringAny(in any) (map[string]any, error) { out := make(map[string]any) switch val := in.(type) { case MapEvent: for k, v := range val { out[k] = v } case utils.MapStorage: for k, v := range val { out[k] = v } case map[string]any: return val, nil default: return nil, errors.New("cannot convert to map[string]any") } return out, nil } // getOptIfaceFromDP is a helper that returns the first option (as interface{}) found in cache or data provider func optIfaceFromDP(dP utils.DataProvider, cch map[string]any, optNames []string) (any, error) { if cch != nil { for _, optName := range optNames { if opt, ok := cch[optName]; ok { return opt, nil } } } values, err := dP.FieldAsInterface([]string{utils.MetaOpts}) if err != nil { return nil, err } opts, err := ConvertOptsToMapStringAny(values) if err != nil { return nil, err } for _, optName := range optNames { if opt, ok := opts[optName]; ok { return opt, nil } } return nil, utils.ErrNotFound }