Make RateProfiles storable in MySQL and Postgres

This commit is contained in:
arberkatellari
2025-11-14 11:39:07 +02:00
committed by Dan Christian Bogos
parent a559563810
commit da41db3f56
13 changed files with 502 additions and 40 deletions

View File

@@ -94,11 +94,11 @@ func TestRateSIT(t *testing.T) {
case utils.MetaMongo:
ratePrfConfigDIR = "rates_mongo"
case utils.MetaRedis:
t.SkipNow()
ratePrfConfigDIR = "rates_redis"
case utils.MetaMySQL:
ratePrfConfigDIR = "rates_mysql"
case utils.MetaPostgres:
t.SkipNow()
ratePrfConfigDIR = "rates_postgres"
default:
t.Fatal("Unknown Database type")
}
@@ -1598,8 +1598,15 @@ func testRateProfileUpdateRates(t *testing.T) {
},
}, &result2); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
} else if *utils.DBType != utils.MetaMySQL && *utils.DBType != utils.MetaPostgres {
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
} else if *utils.DBType == utils.MetaMySQL || *utils.DBType == utils.MetaPostgres {
expectedRate.Rates["RT_THUESDAY"].IntervalRates[0].FixedFee = utils.NewDecimal(2, 1)
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
}
}
@@ -1686,9 +1693,17 @@ func testRateProfileRemoveMultipleRates(t *testing.T) {
},
}, &result2); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
} else if *utils.DBType != utils.MetaMySQL && *utils.DBType != utils.MetaPostgres {
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
} else if *utils.DBType == utils.MetaMySQL || *utils.DBType == utils.MetaPostgres {
expectedRate.Rates["RT_THUESDAY"].IntervalRates[0].FixedFee = utils.NewDecimal(2, 1)
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
}
}
func testRateProfileSetMultipleRatesInProfile(t *testing.T) {
@@ -1878,8 +1893,16 @@ func testRateProfileSetMultipleRatesInProfile(t *testing.T) {
},
}, &result2); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
} else if *utils.DBType != utils.MetaMySQL && *utils.DBType != utils.MetaPostgres {
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
} else if *utils.DBType == utils.MetaMySQL || *utils.DBType == utils.MetaPostgres {
expectedRate.Rates["RT_THUESDAY"].IntervalRates[0].FixedFee = utils.NewDecimal(2, 1)
expectedRate.Rates["RT_SUNDAY"].IntervalRates[1].IntervalStart = utils.NewDecimal(100000000, 0)
if !reflect.DeepEqual(result2, expectedRate) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedRate), utils.ToJSON(result2))
}
}
}
@@ -1937,7 +1960,18 @@ func testRateProfileUpdateProfileRatesOverwrite(t *testing.T) {
},
}, &result2); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result2, ratePrf.RateProfile) {
} else if *utils.DBType != utils.MetaMySQL && *utils.DBType != utils.MetaPostgres {
if !reflect.DeepEqual(result2, ratePrf.RateProfile) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(ratePrf.RateProfile), utils.ToJSON(result2))
}
} else if *utils.DBType == utils.MetaMySQL || *utils.DBType == utils.MetaPostgres {
ratePrf.RateProfile.MaxCost = utils.NewDecimal(5, 1)
if !reflect.DeepEqual(result2, ratePrf.RateProfile) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(ratePrf.RateProfile), utils.ToJSON(result2))
}
}
if !reflect.DeepEqual(result2, ratePrf.RateProfile) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(ratePrf.RateProfile), utils.ToJSON(result2))
}

View File

@@ -180,10 +180,10 @@ const CGRATES_CFG_JSON = `
"*thresholds": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*filters": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*route_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*rate_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
// compatible db types: <*internal|*redis|*mongo>
"*actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*rate_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*load_ids": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "*default"},
"*resource_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "*default"},
"*ip_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "*default"},

View File

@@ -1027,6 +1027,7 @@ func (cfg *CGRConfig) checkConfigSanity() error {
// DataDB sanity checks
hasOneInternalDB := false // used to reutrn error in case more then 1 internaldb is found
allDBsItems := []string{
utils.CacheVersions,
utils.MetaAccounts,
utils.MetaIPProfiles,
utils.MetaIPAllocations,
@@ -1041,6 +1042,7 @@ func (cfg *CGRConfig) checkConfigSanity() error {
utils.MetaThresholds,
utils.MetaFilters,
utils.MetaRouteProfiles,
utils.MetaRateProfiles,
}
for _, dbcfg := range cfg.dbCfg.DBConns {
if dbcfg.Type == utils.MetaInternal {
@@ -1080,11 +1082,11 @@ func (cfg *CGRConfig) checkConfigSanity() error {
if item == utils.MetaCDRs {
if !slices.Contains(storDBTypes, cfg.dbCfg.DBConns[val.DBConn].Type) {
return fmt.Errorf("<%s> db item can only be of types <%v>, got <%s>", item,
storDBTypes, cfg.dbCfg.DBConns[val.DBConn].Type)
storDBTypes[4:], cfg.dbCfg.DBConns[val.DBConn].Type)
}
} else {
if !slices.Contains(dataDBTypes, cfg.dbCfg.DBConns[val.DBConn].Type) {
return fmt.Errorf("<%s> db item can only be of types <%v>, got <%s>", item, dataDBTypes, cfg.dbCfg.DBConns[val.DBConn].Type)
return fmt.Errorf("<%s> db item can only be of types <%v>, got <%s>", item, dataDBTypes[3:], cfg.dbCfg.DBConns[val.DBConn].Type)
}
}
}

View File

@@ -10,11 +10,11 @@
"db_conns": {
"*default": {
"db_type": "*internal",
"opts":{
"internalDBDumpInterval": "500ms",
"internalDBRewriteInterval": "500ms",
"internalDBFileSizeLimit": "4k"
}
"opts":{
"internalDBDumpInterval": "500ms",
"internalDBRewriteInterval": "500ms",
"internalDBFileSizeLimit": "4k"
}
}
},
"items":{

View File

@@ -27,7 +27,8 @@
}
},
"items": {
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"},
"*rate_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},

View File

@@ -0,0 +1,46 @@
{
"general": {
"node_id": "id",
},
"logger": {
"level": 7
},
"db": {
"db_conns": {
"*default": {
"db_type": "redis",
"db_host": "127.0.0.1",
"db_port": 6379,
"db_name": "10",
"db_user": "cgrates"
},
"StorDB": {
"db_type": "postgres",
"db_host": "127.0.0.1",
"db_port": 5432,
"db_name": "cgrates",
"db_user": "cgrates",
"db_password": "CGRateS.org"
}
},
"items": {
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"},
"*rate_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},
"rates": {
"enabled": true,
"prefix_indexed_fields": ["*req.Destination"],
"exists_indexed_fields": ["*req.Destination"],
"rate_prefix_indexed_fields": ["*req.Destination"]
},
"admins": {
"enabled": true
}
}

View File

@@ -0,0 +1,45 @@
{
"general": {
"node_id": "id",
},
"logger": {
"level": 7
},
"db": {
"db_conns": {
"*default": {
"db_type": "redis",
"db_host": "127.0.0.1",
"db_port": 6379,
"db_name": "10",
"db_user": "cgrates"
},
"StorDB": {
"db_type": "mysql",
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "cgrates",
"db_user": "cgrates",
"db_password": "CGRateS.org"
}
},
"items": {
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
}
},
"rates": {
"enabled": true,
"prefix_indexed_fields": ["*req.Destination"],
"exists_indexed_fields": ["*req.Destination"],
"rate_prefix_indexed_fields": ["*req.Destination"]
},
"admins": {
"enabled": true
}
}

View File

@@ -154,4 +154,26 @@ CREATE TABLE route_profiles (
PRIMARY KEY (`pk`),
UNIQUE KEY unique_tenant_id (`tenant`, `id`)
);
CREATE UNIQUE INDEX route_profiles_idx ON route_profiles (`id`);
CREATE UNIQUE INDEX route_profiles_idx ON route_profiles (`id`);
DROP TABLE IF EXISTS rates;
DROP TABLE IF EXISTS rate_profiles;
CREATE TABLE rate_profiles (
`pk` int(11) NOT NULL AUTO_INCREMENT,
`tenant` VARCHAR(40) NOT NULL,
`id` VARCHAR(64) NOT NULL,
`rate_profile` JSON NOT NULL,
PRIMARY KEY (`pk`),
UNIQUE KEY unique_tenant_id (`tenant`, `id`)
);
CREATE UNIQUE INDEX rate_profiles_idx ON rate_profiles (`id`);
CREATE TABLE rates (
`pk` int(11) NOT NULL AUTO_INCREMENT,
`tenant` VARCHAR(40) NOT NULL,
`id` VARCHAR(64) NOT NULL,
`rate` JSON NOT NULL,
`rate_profile_id` VARCHAR(64) NOT NULL,
PRIMARY KEY (`pk`),
UNIQUE KEY unique_tenant_id_rate_profile_id (`tenant`, `id`, `rate_profile_id`),
FOREIGN KEY (rate_profile_id) REFERENCES rate_profiles (id)
);

View File

@@ -152,3 +152,24 @@ CREATE TABLE route_profiles (
UNIQUE (tenant, id)
);
CREATE UNIQUE INDEX route_profiles_idx ON route_profiles ("id");
DROP TABLE IF EXISTS rates;
DROP TABLE IF EXISTS rate_profiles;
CREATE TABLE rate_profiles (
pk SERIAL PRIMARY KEY,
tenant VARCHAR(40) NOT NULL,
id VARCHAR(64) NOT NULL,
rate_profile JSONB NOT NULL,
UNIQUE (tenant, id)
);
CREATE UNIQUE INDEX rate_profiles_idx ON rate_profiles ("id");
CREATE TABLE rates (
pk SERIAL PRIMARY KEY,
tenant VARCHAR(40) NOT NULL,
id VARCHAR(64) NOT NULL,
rate JSONB NOT NULL,
rate_profile_id VARCHAR(64) NOT NULL,
UNIQUE (tenant, id, rate_profile_id),
FOREIGN KEY (rate_profile_id) REFERENCES rate_profiles (id)
);

View File

@@ -536,3 +536,27 @@ type RouteProfileMdl struct {
func (RouteProfileMdl) TableName() string {
return utils.TBLRouteProfiles
}
// Doesnt include Rates in RateProfile json, Rates taken from Rate using foreign keys
type RateProfileJSONMdl struct {
PK uint `gorm:"primary_key"`
Tenant string `index:"0" re:".*"`
ID string `index:"1" re:".*"`
RateProfile utils.JSONB `gorm:"type:jsonb" index:"2" re:".*"`
}
func (RateProfileJSONMdl) TableName() string {
return utils.TBLRateProfiles
}
type RateMdl struct {
PK uint `gorm:"primary_key"`
Tenant string `index:"0" re:".*"`
ID string `index:"1" re:".*"`
Rate utils.JSONB `gorm:"type:jsonb" index:"2" re:".*"`
RateProfileID string `gorm:"foreign_key" index:"3" re:".*"`
}
func (RateMdl) TableName() string {
return utils.TBLRates
}

View File

@@ -127,6 +127,8 @@ func (sqls *SQLStorage) GetKeysForPrefix(ctx *context.Context, prefix string) (k
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLFilters, tntID)
case utils.RouteProfilePrefix:
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLRouteProfiles, tntID)
case utils.RateProfilePrefix:
keys, err = sqls.getAllKeysMatchingTenantID(ctx, utils.TBLRateProfiles, tntID)
default:
err = fmt.Errorf("unsupported prefix in GetKeysForPrefix: %q", prefix)
}
@@ -1040,6 +1042,172 @@ func (sqls *SQLStorage) RemoveRouteProfileDrv(ctx *context.Context, tenant, id s
return
}
func (sqls *SQLStorage) SetRateProfileDrv(ctx *context.Context, rpp *utils.RateProfile, optOverwrite bool) (err error) {
tx := sqls.db.Begin()
rpMdl := &RateProfileJSONMdl{
Tenant: rpp.Tenant,
ID: rpp.ID,
RateProfile: rpp.AsMapStringInterface(),
}
if optOverwrite {
if err = tx.Model(&RateMdl{}).Where(&RateMdl{Tenant: rpMdl.Tenant, RateProfileID: rpMdl.ID}).
Delete(&RateMdl{}).Error; err != nil {
tx.Rollback()
return err
}
if err = tx.Model(&RateProfileJSONMdl{}).Where(
RateProfileJSONMdl{Tenant: rpMdl.Tenant, ID: rpMdl.ID}).Delete(
RateProfileJSONMdl{}).Error; err != nil {
tx.Rollback()
return
}
}
var existingRP RateProfileJSONMdl
result := tx.Where(RateProfileJSONMdl{Tenant: rpMdl.Tenant, ID: rpMdl.ID}).First(&existingRP)
switch result.Error {
case nil: // Record exists, update it
rpMdl.PK = existingRP.PK
if err = tx.Save(rpMdl).Error; err != nil {
tx.Rollback()
return
}
case gorm.ErrRecordNotFound: // Record doesn't exist, create it
if err = tx.Create(rpMdl).Error; err != nil {
tx.Rollback()
return
}
default:
tx.Rollback()
return result.Error
}
for rID, rate := range rpp.Rates {
rMdl := &RateMdl{
Tenant: rpp.Tenant,
ID: rID,
Rate: rate.AsMapStringInterface(),
RateProfileID: rpp.ID,
}
if optOverwrite {
if err = tx.Model(&RateMdl{}).Where(
RateMdl{Tenant: rMdl.Tenant, ID: rMdl.ID}).Delete(
RateMdl{}).Error; err != nil {
tx.Rollback()
return
}
}
var existingRT RateMdl
result := tx.Where(RateMdl{Tenant: rMdl.Tenant, ID: rMdl.ID, RateProfileID: rpMdl.ID}).First(&existingRT)
switch result.Error {
case nil: // Record exists, update it
rMdl.PK = existingRT.PK
if err = tx.Save(rMdl).Error; err != nil {
tx.Rollback()
return
}
case gorm.ErrRecordNotFound: // Record doesn't exist, create it
if err = tx.Create(rMdl).Error; err != nil {
tx.Rollback()
return
}
default:
tx.Rollback()
return result.Error
}
}
tx.Commit()
return
}
func (sqls *SQLStorage) GetRateProfileDrv(ctx *context.Context, tenant, id string) (rpp *utils.RateProfile, err error) {
var rpResult []*RateProfileJSONMdl
if err = sqls.db.Model(&RateProfileJSONMdl{}).Where(&RateProfileJSONMdl{Tenant: tenant,
ID: id}).Find(&rpResult).Error; err != nil {
return nil, err
}
if len(rpResult) == 0 {
return nil, utils.ErrNotFound
}
if rpp, err = utils.MapStringInterfaceToRateProfile(rpResult[0].RateProfile); err != nil {
return nil, err
}
var rtResult []*RateMdl
if err = sqls.db.Model(&RateMdl{}).Where(&RateMdl{Tenant: tenant,
RateProfileID: id}).Find(&rtResult).Error; err != nil { // find all rates for that rating profile
return nil, err
}
if len(rtResult) == 0 {
return nil, utils.ErrNotFound
}
for _, rateMdl := range rtResult {
if rt, err := utils.MapStringInterfaceToRate(rateMdl.Rate); err != nil {
return nil, err
} else {
rpp.Rates[rt.ID] = rt
}
}
return
}
// GetRateProfileRatesDrv will return back all the RateIDs and Rates from a RateProfile
func (sqls *SQLStorage) GetRateProfileRatesDrv(ctx *context.Context, tnt, profileID, rtPrfx string, needIDs bool) (rateIDs []string, rates []*utils.Rate, err error) {
tx := sqls.db.Model(&RateMdl{}).Where(&RateMdl{RateProfileID: profileID})
if rtPrfx != utils.EmptyString {
tx = tx.Where("id LIKE ?", rtPrfx+"%")
}
var rtResult []*RateMdl
if err = tx.Find(&rtResult).Error; err != nil {
return nil, nil, err
}
if len(rtResult) == 0 {
return nil, nil, utils.ErrNotFound
}
for _, ratesMdl := range rtResult {
rateIDs = append(rateIDs, ratesMdl.ID)
}
if needIDs {
// Only return IDs
return rateIDs, nil, nil
}
for _, rateMdl := range rtResult {
if rt, err := utils.MapStringInterfaceToRate(rateMdl.Rate); err != nil {
return nil, nil, err
} else {
rates = append(rates, rt)
}
}
return
}
func (sqls *SQLStorage) RemoveRateProfileDrv(ctx *context.Context, tenant, id string, rateIDs *[]string) (err error) {
tx := sqls.db.Begin()
if rateIDs != nil {
for _, rateID := range *rateIDs {
if err = tx.Model(&RateMdl{}).Where(&RateMdl{Tenant: tenant, ID: rateID, RateProfileID: id}).
Delete(&RateMdl{}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return
}
if err = tx.Model(&RateMdl{}).Where(&RateMdl{Tenant: tenant, RateProfileID: id}).
Delete(&RateMdl{}).Error; err != nil {
tx.Rollback()
return err
}
if err = tx.Model(&RateProfileJSONMdl{}).Where(&RateProfileJSONMdl{Tenant: tenant, ID: id}).
Delete(&RateProfileJSONMdl{}).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return
}
// Used to check if specific subject is stored using prefix key attached to entity
func (sqls *SQLStorage) HasDataDrv(ctx *context.Context, category, subject, tenant string) (has bool, err error) {
var categoryModelMap = map[string]any{
@@ -1057,9 +1225,9 @@ func (sqls *SQLStorage) HasDataDrv(ctx *context.Context, category, subject, tena
utils.RouteProfilePrefix: &RouteProfileMdl{},
utils.AttributeProfilePrefix: &AttributeProfileMdl{},
utils.ChargerProfilePrefix: &ChargerProfileMdl{},
utils.RateProfilePrefix: &RateProfileJSONMdl{},
// utils.TrendPrefix: &TrendJSONMdl{},
// utils.TrendProfilePrefix: &TrendProfileMdl{},
// utils.RateProfilePrefix: &RateProfileJSONMdl{},
}
model, ok := categoryModelMap[category]
if !ok {
@@ -1189,26 +1357,6 @@ func (sqls *SQLStorage) RemoveLoadIDsDrv() (err error) {
return utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) SetRateProfileDrv(ctx *context.Context, rpp *utils.RateProfile, optOverwrite bool) (err error) {
return utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) GetRateProfileDrv(ctx *context.Context, tenant, id string) (rpp *utils.RateProfile, err error) {
return nil, utils.ErrNotImplemented
}
// GetRateProfileRateIDsDrv DataDB method not implemented yet
func (sqls *SQLStorage) GetRateProfileRatesDrv(ctx *context.Context, tnt, profileID, rtPrfx string, needIDs bool) (rateIDs []string, rates []*utils.Rate, err error) {
return nil, nil, utils.ErrNotImplemented
}
// DataDB method not implemented yet
func (sqls *SQLStorage) RemoveRateProfileDrv(ctx *context.Context, tenant, id string, rateIDs *[]string) (err error) {
return utils.ErrNotImplemented
}
// GetIndexesDrv DataDB method not implemented yet
func (sqls *SQLStorage) GetIndexesDrv(ctx *context.Context, idxItmType, tntCtx, idxKey, transactionID string) (indexes map[string]utils.StringSet, err error) {
return nil, utils.ErrNotImplemented

View File

@@ -1926,6 +1926,8 @@ const (
TBLThresholds = "thresholds"
TBLFilters = "filters"
TBLRouteProfiles = "route_profiles"
TBLRateProfiles = "rate_profiles"
TBLRates = "rates"
OldSMCosts = "sm_costs"
TBLTPDispatchers = "tp_dispatcher_profiles"
TBLTPDispatcherHosts = "tp_dispatcher_hosts"

View File

@@ -1026,6 +1026,42 @@ func (rt *Rate) FieldAsInterface(fldPath []string) (_ any, err error) {
}
}
// AsMapStringInterface converts Rate struct to map[string]any
func (rt *Rate) AsMapStringInterface() map[string]any {
if rt == nil {
return nil
}
return map[string]any{
ID: rt.ID,
FilterIDs: rt.FilterIDs,
ActivationTimes: rt.ActivationTimes,
Weights: rt.Weights,
Blocker: rt.Blocker,
IntervalRates: rt.IntervalRates,
}
}
// MapStringInterfaceToRate converts map[string]any to Rate struct
func MapStringInterfaceToRate(m map[string]any) (*Rate, error) {
rt := &Rate{}
if v, ok := m[ID].(string); ok {
rt.ID = v
}
rt.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
if v, ok := m[ActivationTimes].(string); ok {
rt.ActivationTimes = v
}
rt.Weights = InterfaceToDynamicWeights(m[Weights])
if v, ok := m[Blocker].(bool); ok {
rt.Blocker = v
}
var err error
if rt.IntervalRates, err = InterfaceToIntervalRates(m[IntervalRates]); err != nil {
return nil, err
}
return rt, nil
}
func (iR *IntervalRate) String() string { return ToJSON(iR) }
func (iR *IntervalRate) FieldAsString(fldPath []string) (_ string, err error) {
var val any
@@ -1054,6 +1090,42 @@ func (iR *IntervalRate) FieldAsInterface(fldPath []string) (_ any, err error) {
}
}
// InterfaceToIntervalRates converts any to []*IntervalRate
func InterfaceToIntervalRates(v any) (intervalRates []*IntervalRate, err error) {
if v == nil {
return
}
switch val := v.(type) {
case []*IntervalRate:
return val, nil
case []any:
result := make([]*IntervalRate, 0, len(val))
for _, item := range val {
if irMap, ok := item.(map[string]any); ok {
ir := new(IntervalRate)
if ir.IntervalStart, err = NewDecimalFromInterface(irMap[IntervalStart]); err != nil {
return nil, err
}
if ir.FixedFee, err = NewDecimalFromInterface(irMap[FixedFee]); err != nil {
return nil, err
}
if ir.RecurrentFee, err = NewDecimalFromInterface(irMap[RecurrentFee]); err != nil {
return nil, err
}
if ir.Unit, err = NewDecimalFromInterface(irMap[Unit]); err != nil {
return nil, err
}
if ir.Increment, err = NewDecimalFromInterface(irMap[Increment]); err != nil {
return nil, err
}
result = append(result, ir)
}
}
return result, nil
}
return
}
// AsDataDBMap is used to is a convert method in order to properly set trough a hasmap in redis server our rate profile
func (rp *RateProfile) AsDataDBMap(ms Marshaler) (mp map[string]any, err error) {
mp = map[string]any{
@@ -1089,6 +1161,51 @@ func (rp *RateProfile) AsDataDBMap(ms Marshaler) (mp map[string]any, err error)
return mp, nil
}
// AsMapStringInterface converts RateProfile struct to map[string]any
//
// ! Rates not included !
func (rp *RateProfile) AsMapStringInterface() map[string]any {
if rp == nil {
return nil
}
return map[string]any{
Tenant: rp.Tenant,
ID: rp.ID,
FilterIDs: rp.FilterIDs,
Weights: rp.Weights,
MinCost: rp.MinCost,
MaxCost: rp.MaxCost,
MaxCostStrategy: rp.MaxCostStrategy,
}
}
// MapStringInterfaceToRateProfile converts map[string]any to RateProfile struct
//
// ! Rates not included !
func MapStringInterfaceToRateProfile(m map[string]any) (*RateProfile, error) {
rp := &RateProfile{}
if v, ok := m[Tenant].(string); ok {
rp.Tenant = v
}
if v, ok := m[ID].(string); ok {
rp.ID = v
}
rp.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
rp.Weights = InterfaceToDynamicWeights(m[Weights])
var err error
if rp.MinCost, err = NewDecimalFromInterface(m[MinCost]); err != nil {
return nil, err
}
if rp.MaxCost, err = NewDecimalFromInterface(m[MaxCost]); err != nil {
return nil, err
}
if v, ok := m[MaxCostStrategy].(string); ok {
rp.MaxCostStrategy = v
}
rp.Rates = make(map[string]*Rate)
return rp, nil
}
// NewRateProfileFromMapDataDBMap will convert a RateProfile map into a RatePRofile struct. This is used when we get the map from redis database
func NewRateProfileFromMapDataDBMap(tnt, id string, mapRP map[string]any, ms Marshaler) (rp *RateProfile, err error) {
rp = &RateProfile{