ActionProfile modifications

This commit is contained in:
arberkatellari
2025-07-16 12:20:46 +02:00
committed by Dan Christian Bogos
parent e3bf9e10ec
commit 38e7c7eb39
12 changed files with 339 additions and 91 deletions

View File

@@ -66,9 +66,8 @@ func (aL *actSetBalance) execute(ctx *context.Context, data utils.MapStorage, tr
if val, err = rsr.ParseDataProvider(data); err != nil {
return
}
args.Diktats[i] = &utils.BalDiktat{
Path: actD.Path,
Path: utils.IfaceAsString(actD.Opts[utils.MetaBalancePath]),
Value: val,
}
}
@@ -106,7 +105,7 @@ func (aL *actRemBalance) execute(ctx *context.Context, data utils.MapStorage, tr
APIOpts: aL.cfg().Opts,
}
for i, actD := range aL.cfg().Diktats {
args.BalanceIDs[i] = actD.Path
args.BalanceIDs[i] = utils.IfaceAsString(actD.Opts[utils.MetaBalancePath])
}
var rply string
return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AccountSConns,

View File

@@ -45,9 +45,9 @@ func parseParamStringToMap(paramStr string, targetMap map[string]any) error {
return nil
}
// actDynamicThreshold processes the `ActionValue` field from the action to construct a Threshold profile
// actDynamicThreshold processes the `ActionDiktatsOpts` field from the action to construct a Threshold profile
//
// The ActionValue field format is expected as follows:
// The ActionDiktatsOpts field format is expected as follows:
//
// 0 Tenant: string
// 1 ID: string
@@ -91,7 +91,8 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora
if len(aL.aCfg.Diktats) == 0 {
return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID)
}
params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep)
params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]),
utils.InfieldSep)
if len(params) != 11 {
return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params))
}
@@ -181,9 +182,9 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora
utils.AdminSv1SetThresholdProfile, args, &rply)
}
// actDynamicStats processes the `ActionValue` field from the action to construct a StatQueueProfile
// actDynamicStats processes the `ActionDiktatsOpts` field from the action to construct a StatQueueProfile
//
// The ActionValue field format is expected as follows:
// The ActionDiktatsOpts field format is expected as follows:
//
// 0 Tenant: string
// 1 ID: string
@@ -229,7 +230,8 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage,
if len(aL.aCfg.Diktats) == 0 {
return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID)
}
params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep)
params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]),
utils.InfieldSep)
if len(params) != 14 {
return fmt.Errorf("invalid number of parameters <%d> expected 14", len(params))
}
@@ -364,9 +366,9 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage,
utils.AdminSv1SetStatQueueProfile, args, &rply)
}
// actDynamicAttribute processes the `ActionValue` field from the action to construct a AttributeProfile
// actDynamicAttribute processes the `ActionDiktatsOpts` field from the action to construct a AttributeProfile
//
// The ActionValue field format is expected as follows:
// The ActionDiktatsOpts field format is expected as follows:
//
// 0 Tenant: string
// 1 ID: string
@@ -409,7 +411,8 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora
if len(aL.aCfg.Diktats) == 0 {
return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID)
}
params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep)
params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]),
utils.InfieldSep)
if len(params) != 11 {
return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params))
}
@@ -509,9 +512,9 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora
utils.AdminSv1SetAttributeProfile, args, &rply)
}
// actDynamicResource processes the `ActionValue` field from the action to construct a ResourceProfile
// actDynamicResource processes the `ActionDiktatsOpts` field from the action to construct a ResourceProfile
//
// The ActionValue field format is expected as follows:
// The ActionDiktatsOpts field format is expected as follows:
//
// 0 Tenant: string
// 1 ID: string
@@ -554,7 +557,8 @@ func (aL *actDynamicResource) execute(ctx *context.Context, data utils.MapStorag
if len(aL.aCfg.Diktats) == 0 {
return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID)
}
params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep)
params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]),
utils.InfieldSep)
if len(params) != 11 {
return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params))
}

View File

@@ -43,7 +43,9 @@ func newActHTTPPost(ctx *context.Context, tnt string, cgrEv *utils.CGREvent,
if err != nil {
return nil, err
}
eeCfg := config.NewEventExporterCfg(aL.id(), "", actD.Path, cfg.EEsCfg().ExporterCfg(utils.MetaDefault).FailedPostsDir,
eeCfg := config.NewEventExporterCfg(aL.id(), utils.EmptyString,
utils.IfaceAsString(actD.Opts[utils.MetaURL]),
cfg.EEsCfg().ExporterCfg(utils.MetaDefault).FailedPostsDir,
attempts, nil)
aL.pstrs[i], _ = ees.NewHTTPjsonMapEE(eeCfg, cfg, nil, nil)
}

View File

@@ -120,6 +120,14 @@ func (admS *AdminS) V1SetActionProfile(ctx *context.Context, ap *utils.ActionPro
if missing := utils.MissingStructFields(ap.ActionProfile, []string{utils.ID, utils.Actions}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
for i := range ap.ActionProfile.Actions {
for j := range ap.ActionProfile.Actions[i].Diktats { // if there are diktats, make sure their ID exists
if missing := utils.MissingStructFields(ap.ActionProfile.Actions[i].
Diktats[j], []string{utils.ID}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
}
}
if ap.Tenant == utils.EmptyString {
ap.Tenant = admS.cfg.GeneralCfg().DefaultTenant
}

View File

@@ -121,6 +121,17 @@ func (admS *AdminSv1) SetActionProfile(ctx *context.Context, ap *utils.ActionPro
if missing := utils.MissingStructFields(ap.ActionProfile, []string{utils.ID, utils.Actions}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
for i := range ap.ActionProfile.Actions {
if ap.ActionProfile.Actions[i] == nil {
continue
}
for j := range ap.ActionProfile.Actions[i].Diktats { // if there are diktats, make sure their ID exists
if missing := utils.MissingStructFields(ap.ActionProfile.Actions[i].
Diktats[j], []string{utils.ID}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
}
}
if ap.Tenant == utils.EmptyString {
ap.Tenant = admS.cfg.GeneralCfg().DefaultTenant
}

View File

@@ -1646,8 +1646,13 @@ const CGRATES_CFG_JSON = `
{"tag": "ActionTTL", "path": "Actions[<~*req.8>].TTL", "type": "*variable", "value": "~*req.10", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionType", "path": "Actions[<~*req.8>].Type", "type": "*variable", "value": "~*req.11", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionOpts", "path": "Actions[<~*req.8>].Opts", "type": "*variable", "value": "~*req.12", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionPath", "path": "Actions[<~*req.8>].Diktats.Path", "type": "*variable", "value": "~*req.13","new_branch":true, "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionValue", "path": "Actions[<~*req.8>].Diktats.Value", "type": "*variable", "value": "~*req.14", "filters": ["*notempty:~*req.8:"]}
{"tag": "ActionWeights", "path": "Actions[<~*req.8>].Weights", "type": "*variable", "value": "~*req.13", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionBlockers", "path": "Actions[<~*req.8>].Blockers", "type": "*variable", "value": "~*req.14", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionDiktatsID", "path": "Actions[<~*req.8>].Diktats.ID", "type": "*variable", "value": "~*req.15","new_branch":true, "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionDiktatsFilterIDs", "path": "Actions[<~*req.8>].Diktats.FilterIDs", "type": "*variable", "value": "~*req.16", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionDiktatsOpts", "path": "Actions[<~*req.8>].Diktats.Opts", "type": "*variable", "value": "~*req.17", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionDiktatsWeights", "path": "Actions[<~*req.8>].Diktats.Weights", "type": "*variable", "value": "~*req.18", "filters": ["*notempty:~*req.8:"]},
{"tag": "ActionDiktatsBlockers", "path": "Actions[<~*req.8>].Diktats.Blockers", "type": "*variable", "value": "~*req.19", "filters": ["*notempty:~*req.8:"]},
]
},
{

View File

@@ -2024,7 +2024,9 @@ func (apm ActionProfileMdls) CSVHeader() (result []string) {
return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs,
utils.Weights, utils.Blockers, utils.Schedule, utils.TargetType, utils.TargetIDs,
utils.ActionID, utils.ActionFilterIDs, utils.ActionTTL,
utils.ActionType, utils.ActionOpts, utils.ActionPath, utils.ActionValue,
utils.ActionType, utils.ActionOpts, utils.ActionWeights, utils.ActionBlockers,
utils.ActionDiktatsID, utils.ActionDiktatsFilterIDs, utils.ActionDiktatsOpts,
utils.ActionDiktatsWeights, utils.ActionDiktatsBlockers,
}
}
@@ -2074,19 +2076,45 @@ func (apm ActionProfileMdls) AsTPActionProfile() (result []*utils.TPActionProfil
Type: tp.ActionType,
Opts: tp.ActionOpts,
Diktats: []*utils.TPAPDiktat{{
Path: tp.ActionPath,
Value: tp.ActionValue,
ID: tp.ActionDiktatsID,
Opts: tp.ActionDiktatsOpts,
}},
}
if tp.ActionFilterIDs != utils.EmptyString {
tpAAction.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionFilterIDs, utils.InfieldSep)).AsSlice()
}
if tp.ActionWeights != utils.EmptyString {
tpAAction.Weights = tp.ActionWeights
}
if tp.ActionBlockers != utils.EmptyString {
tpAAction.Blockers = tp.ActionBlockers
}
if tp.ActionDiktatsFilterIDs != utils.EmptyString {
tpAAction.Diktats[0].FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice()
}
if tp.ActionDiktatsWeights != utils.EmptyString {
tpAAction.Diktats[0].Weights = tp.ActionDiktatsWeights
}
if tp.ActionDiktatsBlockers != utils.EmptyString {
tpAAction.Diktats[0].Blockers = tp.ActionDiktatsBlockers
}
aPrf.Actions = append(aPrf.Actions, tpAAction)
} else {
aPrf.Actions[lacts-1].Diktats = append(aPrf.Actions[lacts-1].Diktats, &utils.TPAPDiktat{
Path: tp.ActionPath,
Value: tp.ActionValue,
})
diktat := &utils.TPAPDiktat{
ID: tp.ActionDiktatsID,
Opts: tp.ActionDiktatsOpts,
}
if tp.ActionDiktatsFilterIDs != utils.EmptyString {
diktat.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice()
}
if tp.ActionDiktatsWeights != utils.EmptyString {
diktat.Weights = tp.ActionDiktatsWeights
}
if tp.ActionDiktatsBlockers != utils.EmptyString {
diktat.Blockers = tp.ActionDiktatsBlockers
}
aPrf.Actions[lacts-1].Diktats = append(aPrf.Actions[lacts-1].Diktats,
diktat)
}
}
actPrfMap[tenID] = aPrf
@@ -2129,6 +2157,8 @@ func APItoModelTPActionProfile(tPrf *utils.TPActionProfile) (mdls ActionProfileM
mdl.ActionTTL = action.TTL
mdl.ActionType = action.Type
mdl.ActionOpts = action.Opts
mdl.ActionWeights = action.Weights
mdl.ActionBlockers = action.Blockers
for j, actD := range action.Diktats {
if j != 0 {
mdl = &ActionProfileMdl{
@@ -2139,8 +2169,11 @@ func APItoModelTPActionProfile(tPrf *utils.TPActionProfile) (mdls ActionProfileM
ActionType: mdl.ActionType,
}
}
mdl.ActionPath = actD.Path
mdl.ActionValue = actD.Value
mdl.ActionDiktatsID = actD.ID
mdl.ActionDiktatsFilterIDs = strings.Join(actD.FilterIDs, utils.InfieldSep)
mdl.ActionDiktatsOpts = actD.Opts
mdl.ActionDiktatsWeights = actD.Weights
mdl.ActionDiktatsBlockers = actD.Blockers
}
mdls = append(mdls, mdl)
}
@@ -2173,9 +2206,33 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils
for i, act := range tpAp.Actions {
actDs := make([]*utils.APDiktat, len(act.Diktats))
for j, actD := range act.Diktats {
if actD.ID == utils.EmptyString {
return nil, fmt.Errorf("missing ID from Diktats of ActionProfile <%s> Action <%s>", ap.TenantID(), actD.ID)
}
actDs[j] = &utils.APDiktat{
Path: actD.Path,
Value: actD.Value,
ID: actD.ID,
FilterIDs: actD.FilterIDs,
}
if actD.Opts != utils.EmptyString {
actDs[j].Opts = make(map[string]any)
for opt := range strings.SplitSeq(actD.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2)
if len(keyValSls) != 2 {
return nil, fmt.Errorf("malformed option for ActionProfile <%s> for action <%s> for diktat <%s>", ap.TenantID(), actD.ID, actD.ID)
}
actDs[j].Opts[keyValSls[0]] = keyValSls[1]
}
}
if actD.Weights != utils.EmptyString {
if actDs[j].Weights, err = utils.NewDynamicWeightsFromString(actD.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
return
}
}
if actD.Blockers != utils.EmptyString {
if actDs[j].Blockers, err = utils.NewDynamicBlockersFromString(actD.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
return
}
}
}
ap.Actions[i] = &utils.APAction{
@@ -2189,8 +2246,8 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils
}
if act.Opts != utils.EmptyString {
ap.Actions[i].Opts = make(map[string]any)
for _, opt := range strings.Split(act.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
keyValSls := utils.SplitConcatenatedKey(opt)
for opt := range strings.SplitSeq(act.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2)
if len(keyValSls) != 2 {
err = fmt.Errorf("malformed option for ActionProfile <%s> for action <%s>", ap.TenantID(), act.ID)
return
@@ -2198,7 +2255,16 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils
ap.Actions[i].Opts[keyValSls[0]] = keyValSls[1]
}
}
if act.Weights != utils.EmptyString {
if ap.Actions[i].Weights, err = utils.NewDynamicWeightsFromString(act.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
return
}
}
if act.Blockers != utils.EmptyString {
if ap.Actions[i].Blockers, err = utils.NewDynamicBlockersFromString(act.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
return
}
}
}
return
}
@@ -2221,9 +2287,16 @@ func ActionProfileToAPI(ap *utils.ActionProfile) (tpAp *utils.TPActionProfile) {
for i, act := range ap.Actions {
actDs := make([]*utils.TPAPDiktat, len(act.Diktats))
for j, actD := range act.Diktats {
elems := make([]string, 0, len(actD.Opts))
for k, v := range actD.Opts {
elems = append(elems, utils.ConcatenatedKey(k, utils.IfaceAsString(v)))
}
actDs[j] = &utils.TPAPDiktat{
Path: actD.Path,
Value: actD.Value,
ID: actD.ID,
FilterIDs: actD.FilterIDs,
Opts: strings.Join(elems, utils.InfieldSep),
Weights: actD.Weights.String(utils.InfieldSep, utils.ANDSep),
Blockers: actD.Blockers.String(utils.InfieldSep, utils.ANDSep),
}
}
@@ -2236,8 +2309,10 @@ func ActionProfileToAPI(ap *utils.ActionProfile) (tpAp *utils.TPActionProfile) {
FilterIDs: act.FilterIDs,
TTL: act.TTL.String(),
Type: act.Type,
Diktats: actDs,
Opts: strings.Join(elems, utils.InfieldSep),
Weights: act.Weights.String(utils.InfieldSep, utils.ANDSep),
Blockers: act.Blockers.String(utils.InfieldSep, utils.ANDSep),
Diktats: actDs,
}
}
return

View File

@@ -325,23 +325,28 @@ func (RateProfileMdl) TableName() string {
}
type ActionProfileMdl struct {
PK uint `gorm:"primary_key"`
Tpid string
Tenant string `index:"0" re:".*"`
ID string `index:"1" re:".*"`
FilterIDs string `index:"2" re:".*"`
Weights string `index:"3" re:".*"`
Blockers string `index:"4" re:".*"`
Schedule string `index:"5" re:".*"`
TargetType string `index:"6" re:".*"`
TargetIDs string `index:"7" re:".*"`
ActionID string `index:"8" re:".*"`
ActionFilterIDs string `index:"9" re:".*"`
ActionTTL string `index:"10" re:".*"`
ActionType string `index:"11" re:".*"`
ActionOpts string `index:"12" re:".*"`
ActionPath string `index:"13" re:".*"`
ActionValue string `index:"14" re:".*"`
PK uint `gorm:"primary_key"`
Tpid string
Tenant string `index:"0" re:".*"`
ID string `index:"1" re:".*"`
FilterIDs string `index:"2" re:".*"`
Weights string `index:"3" re:".*"`
Blockers string `index:"4" re:".*"`
Schedule string `index:"5" re:".*"`
TargetType string `index:"6" re:".*"`
TargetIDs string `index:"7" re:".*"`
ActionID string `index:"8" re:".*"`
ActionFilterIDs string `index:"9" re:".*"`
ActionTTL string `index:"10" re:".*"`
ActionType string `index:"11" re:".*"`
ActionOpts string `index:"12" re:".*"`
ActionWeights string `index:"13" re:".*"`
ActionBlockers string `index:"14" re:".*"`
ActionDiktatsID string `index:"15" re:".*"`
ActionDiktatsFilterIDs string `index:"16" re:".*"`
ActionDiktatsOpts string `index:"17" re:".*"`
ActionDiktatsWeights string `index:"18" re:".*"`
ActionDiktatsBlockers string `index:"19" re:".*"`
CreatedAt time.Time
}

View File

@@ -45,7 +45,12 @@ func (tpActs TPActions) exportItems(ctx *context.Context, wrtr io.Writer, tnt st
csvWriter := csv.NewWriter(wrtr)
csvWriter.Comma = utils.CSVSep
// before writing the profiles, we must write the headers
if err = csvWriter.Write([]string{"#Tenant", "ID", "FilterIDs", "Weights", "Blockers", "Schedule", "TargetType", "TargetIDs", "ActionID", "ActionFilterIDs", "ActionTTL", "ActionType", "ActionOpts", "ActionPath", "ActionValue"}); err != nil {
if err = csvWriter.Write([]string{"#" + utils.Tenant, utils.ID, utils.FilterIDs,
utils.Weights, utils.Blockers, utils.Schedule, utils.TargetType, utils.TargetIDs,
utils.ActionID, utils.ActionFilterIDs, utils.ActionTTL,
utils.ActionType, utils.ActionOpts, utils.ActionWeights, utils.ActionBlockers,
utils.ActionDiktatsID, utils.ActionDiktatsFilterIDs, utils.ActionDiktatsOpts,
utils.ActionDiktatsWeights, utils.ActionDiktatsBlockers}); err != nil {
return
}
for _, actsID := range itmIDs {

View File

@@ -20,6 +20,7 @@ package utils
import (
"maps"
"slices"
"strconv"
"strings"
"time"
@@ -297,6 +298,8 @@ type APAction struct {
TTL time.Duration // Cancel Action if not executed within TTL
Type string // Type of Action
Opts map[string]any // Extra options to pass depending on action type
Weights DynamicWeights
Blockers DynamicBlockers
Diktats []*APDiktat
}
@@ -318,6 +321,12 @@ func (a *APAction) Clone() *APAction {
cloned.Opts = make(map[string]any, len(a.Opts))
maps.Copy(cloned.Opts, a.Opts)
}
if a.Weights != nil {
cloned.Weights = a.Weights.Clone()
}
if a.Blockers != nil {
cloned.Blockers = a.Blockers.Clone()
}
if a.Diktats != nil {
cloned.Diktats = make([]*APDiktat, len(a.Diktats))
for i, diktat := range a.Diktats {
@@ -336,6 +345,12 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) {
if path[0] == Opts {
return MapStorage(a.Opts).Set(path[1:], val)
}
if path[0] == Diktats && path[1] == Opts {
if len(a.Diktats) == 0 || newBranch {
a.Diktats = append(a.Diktats, new(APDiktat))
}
return MapStorage(a.Diktats[len(a.Diktats)-1].Opts).Set(path[2:], val)
}
return ErrWrongPath
case 0:
return ErrWrongPath
@@ -360,6 +375,14 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) {
a.TTL, err = IfaceAsDuration(val)
case Opts:
a.Opts, err = NewMapFromCSV(IfaceAsString(val))
case Weights:
if val != EmptyString {
a.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
}
case Blockers:
if val != EmptyString {
a.Blockers, err = NewDynamicBlockersFromString(IfaceAsString(val), InfieldSep, ANDSep)
}
}
case 2:
switch path[0] {
@@ -372,10 +395,34 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) {
a.Diktats = append(a.Diktats, new(APDiktat))
}
switch path[1] {
case Path:
a.Diktats[len(a.Diktats)-1].Path = IfaceAsString(val)
case Value:
a.Diktats[len(a.Diktats)-1].Value = IfaceAsString(val)
default:
if strings.HasPrefix(path[1], Opts) &&
path[1][4] == '[' && path[1][len(path[1])-1] == ']' {
a.Opts[path[1][5:len(path[1])-1]] = val
return
}
return ErrWrongPath
case ID:
if dID := IfaceAsString(val); dID == EmptyString {
return ErrWrongPath
} else {
a.Diktats[len(a.Diktats)-1].ID = dID
}
case FilterIDs:
var valA []string
valA, err = IfaceAsStringSlice(val)
a.Diktats[len(a.Diktats)-1].FilterIDs = append(a.Diktats[len(a.Diktats)-1].
FilterIDs, valA...)
case Opts:
a.Diktats[len(a.Diktats)-1].Opts, err = NewMapFromCSV(IfaceAsString(val))
case Weights:
if val != EmptyString {
a.Diktats[len(a.Diktats)-1].Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
}
case Blockers:
if val != EmptyString {
a.Diktats[len(a.Diktats)-1].Blockers, err = NewDynamicBlockersFromString(IfaceAsString(val), InfieldSep, ANDSep)
}
}
}
}
@@ -387,23 +434,31 @@ func (a *APAction) Merge(v2 *APAction) {
if len(v2.ID) != 0 {
a.ID = v2.ID
}
a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...)
if v2.TTL != 0 {
a.TTL = v2.TTL
}
if len(v2.Type) != 0 {
a.Type = v2.Type
}
for key, value := range v2.Opts {
a.Opts[key] = value
maps.Copy(a.Opts, v2.Opts)
if v2.Blockers != nil {
a.Blockers = append(a.Blockers, v2.Blockers...)
}
a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...)
if len(a.Diktats) == 1 && a.Diktats[0].Path == EmptyString {
if v2.Weights != nil {
a.Weights = append(a.Weights, v2.Weights...)
}
if len(a.Diktats) == 1 && len(a.Diktats[0].Opts) == 0 {
a.Diktats = a.Diktats[:0]
}
for _, diktat := range v2.Diktats {
if diktat.Path != EmptyString {
a.Diktats = append(a.Diktats, diktat)
for _, diktatV2 := range v2.Diktats {
if idx := slices.IndexFunc(a.Diktats, func(a *APDiktat) bool {
return a.ID == diktatV2.ID
}); idx != -1 {
a.Diktats[idx].Merge(diktatV2)
continue
}
a.Diktats = append(a.Diktats, diktatV2)
}
}
@@ -472,19 +527,20 @@ func (a *APAction) FieldAsInterface(fldPath []string) (_ any, err error) {
return a.Type, nil
case Opts:
return a.Opts, nil
case Weights:
return a.Weights, nil
case Blockers:
return a.Blockers, nil
}
case 2:
fld, idxStr := GetPathIndexString(fldPath[0])
switch fld {
default:
if fld, _ := GetPathIndexString(fldPath[0]); fld != Opts {
return nil, ErrNotFound
case Opts:
path := fldPath[1:]
if idxStr != nil {
path = append([]string{*idxStr}, path...)
}
return MapStorage(a.Opts).FieldAsInterface(path)
case Diktats:
}
return MapStorage(a.Opts).FieldAsInterface(fldPath[1:])
case 3:
if fld, idxStr := GetPathIndexString(fldPath[0]); fld != Diktats {
return nil, ErrNotFound
} else {
if idxStr == nil {
return nil, ErrNotFound
}
@@ -502,8 +558,11 @@ func (a *APAction) FieldAsInterface(fldPath []string) (_ any, err error) {
// APDiktat defines a path and value operation to be executed by an action.
type APDiktat struct {
Path string // Path to execute
Value string // Value to execute on Path
ID string // Diktat ID
FilterIDs []string // Diktat FilterIDs
Opts map[string]any // Diktat options to pass
Weights DynamicWeights
Blockers DynamicBlockers
valRSR RSRParsers
}
@@ -514,8 +573,21 @@ func (d *APDiktat) Clone() *APDiktat {
return nil
}
cloned := &APDiktat{
Path: d.Path,
Value: d.Value,
ID: d.ID,
}
if d.FilterIDs != nil {
cloned.FilterIDs = make([]string, len(d.FilterIDs))
copy(cloned.FilterIDs, d.FilterIDs)
}
if d.Opts != nil {
cloned.Opts = make(map[string]any, len(d.Opts))
maps.Copy(cloned.Opts, d.Opts)
}
if d.Weights != nil {
cloned.Weights = d.Weights.Clone()
}
if d.Blockers != nil {
cloned.Blockers = d.Blockers.Clone()
}
if d.valRSR != nil {
cloned.valRSR = d.valRSR.Clone()
@@ -523,10 +595,20 @@ func (d *APDiktat) Clone() *APDiktat {
return cloned
}
// Merge combines the values from another APDiktat into this one.
func (a *APDiktat) Merge(v2 *APDiktat) {
a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...)
maps.Copy(a.Opts, v2.Opts)
a.Blockers = append(a.Blockers, v2.Blockers...)
a.Weights = append(a.Weights, v2.Weights...)
}
// RSRValues returns the Value as RSRParsers or creates new ones if not initialized.
func (dk *APDiktat) RSRValues() (RSRParsers, error) {
if dk.valRSR == nil {
return NewRSRParsers(dk.Value, RSRSep)
if _, has := dk.Opts[MetaBalanceValue]; has {
return NewRSRParsers(IfaceAsString(dk.Opts[MetaBalanceValue]), RSRSep)
}
}
return dk.valRSR, nil
}
@@ -545,15 +627,52 @@ func (dk *APDiktat) FieldAsString(fldPath []string) (_ string, err error) {
// FieldAsInterface implements the DataProvider interface, retrieving field value as interface.
func (dk *APDiktat) FieldAsInterface(fldPath []string) (_ any, err error) {
if len(fldPath) != 1 {
return nil, ErrNotFound
}
switch fldPath[0] {
switch len(fldPath) {
default:
if fld, idxStr := GetPathIndexString(fldPath[0]); fld == Opts {
path := fldPath[1:]
if idxStr != nil {
path = append([]string{*idxStr}, path...)
}
return MapStorage(dk.Opts).FieldAsInterface(path)
}
fallthrough
case 0:
return nil, ErrNotFound
case Path:
return dk.Path, nil
case Value:
return dk.Value, nil
case 1:
switch fldPath[0] {
default:
fld, idxStr := GetPathIndexString(fldPath[0])
if idxStr != nil {
switch fld {
case FilterIDs:
var idx int
if idx, err = strconv.Atoi(*idxStr); err != nil {
return
}
if idx < len(dk.FilterIDs) {
return dk.FilterIDs[idx], nil
}
case Opts:
return MapStorage(dk.Opts).FieldAsInterface([]string{*idxStr})
}
}
return nil, ErrNotFound
case ID:
return dk.ID, nil
case FilterIDs:
return dk.FilterIDs, nil
case Opts:
return dk.Opts, nil
case Weights:
return dk.Weights, nil
case Blockers:
return dk.Blockers, nil
}
case 2:
if fld, _ := GetPathIndexString(fldPath[0]); fld != Opts {
return nil, ErrNotFound
}
return MapStorage(dk.Opts).FieldAsInterface(fldPath[1:])
}
}

View File

@@ -687,12 +687,17 @@ type TPAPAction struct {
TTL string
Type string
Opts string
Weights string
Blockers string
Diktats []*TPAPDiktat
}
type TPAPDiktat struct {
Path string
Value string
ID string
FilterIDs []string
Opts string
Weights string
Blockers string
}
type TPAccount struct {

View File

@@ -834,7 +834,13 @@ const (
ActionFilterIDs = "ActionFilterIDs"
ActionTTL = "ActionTTL"
ActionOpts = "ActionOpts"
ActionPath = "ActionPath"
ActionWeights = "ActionWeights"
ActionBlockers = "ActionBlockers"
ActionDiktatsID = "ActionDiktatsID"
ActionDiktatsFilterIDs = "ActionDiktatsFilterIDs"
ActionDiktatsOpts = "ActionDiktatsOpts"
ActionDiktatsWeights = "ActionDiktatsWeights"
ActionDiktatsBlockers = "ActionDiktatsBlockers"
TPid = "TPid"
LoadId = "LoadId"
ActionPlanId = "ActionPlanId"
@@ -1131,7 +1137,6 @@ const (
MetaRemoteSetAccount = "*remote_set_account"
ActionID = "ActionID"
ActionType = "ActionType"
ActionValue = "ActionValue"
BalanceValue = "BalanceValue"
BalanceUnits = "BalanceUnits"
BalanceUnitFactors = "BalanceUnitFactors"
@@ -1145,6 +1150,11 @@ const (
MetaDynamicStats = "*dynamic_stats"
MetaDynamicAttribute = "*dynamic_attribute"
MetaDynamicResource = "*dynamic_resource"
// Diktats Opts Fields
MetaBalancePath = "*balancePath"
MetaBalanceValue = "*balanceValue"
MetaURL = "*url"
)
// Migrator Metas