From 41b9b719d57eb70c2fc62572b0d2bd63a2a6b394 Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Wed, 6 Mar 2024 12:05:48 -0500 Subject: [PATCH] Ensure AccountSummary is of *AccountSummary type when parsing ddp --- engine/account.go | 82 +++++++++++++++++++++++++----------- engine/dynamicdp.go | 42 +++++------------- engine/eventcost.go | 33 +++++++++++++++ general_tests/ees_it_test.go | 11 +++++ 4 files changed, 113 insertions(+), 55 deletions(-) diff --git a/engine/account.go b/engine/account.go index 97b014732..ef5d8942f 100644 --- a/engine/account.go +++ b/engine/account.go @@ -1084,7 +1084,9 @@ func (acc *Account) Publish(initBal map[string]float64) { Tenant: acntSummary.Tenant, ID: utils.GenUUID(), Time: utils.TimePointer(time.Now()), - Event: acntSummary.AsMapInterface(), + Event: map[string]any{ + utils.AccountSummary: acntSummary, + }, APIOpts: map[string]any{ utils.MetaEventType: utils.AccountUpdate, }, @@ -1195,29 +1197,6 @@ func (as *AccountSummary) SetInitialValue(old *AccountSummary) { } } -// GetBalanceWithID returns a Balance given balance type and balance ID -func (acc *Account) GetBalanceWithID(blcType, blcID string) (blc *Balance) { - for _, blc = range acc.BalanceMap[blcType] { - if blc.ID == blcID { - return - } - } - return nil -} - -// FindBalanceByID searches through all balance types for a balance with the -// specified ID and returns it alongside its type. -func (acc *Account) FindBalanceByID(balanceID string) (blnc *Balance, blncType string) { - for balanceType, balances := range acc.BalanceMap { - for _, balance := range balances { - if balance.ID == balanceID { - return balance, balanceType - } - } - } - return nil, "" -} - // FieldAsInterface func to help EventCost FieldAsInterface func (as *AccountSummary) FieldAsInterface(fldPath []string) (val any, err error) { if as == nil || len(fldPath) == 0 { @@ -1297,6 +1276,61 @@ func (as *AccountSummary) AsMapInterface() map[string]any { } } +// processAccountSummaryField ensures accSummary is an AccountSummary and calls FieldAsInterface on it. +func processAccountSummaryField(fldPath []string, accSummary any, event map[string]any) (any, error) { + var err error + var accSummaryBytes []byte + switch accSum := accSummary.(type) { + case *AccountSummary: + // Directly proceed if already *AccountSummary. + return accSum.FieldAsInterface(fldPath) + case string: + // Convert string to bytes for unmarshalling + // if it's a serialized *AccountSummary. + accSummaryBytes = []byte(accSum) + default: + // Marshal non-string types to JSON bytes + // for unmarshalling into *AccountSummary. + accSummaryBytes, err = json.Marshal(accSum) + if err != nil { + return nil, err + } + } + var as AccountSummary + if err = json.Unmarshal(accSummaryBytes, &as); err != nil { + return nil, err + } + + // Update AccountSummary with the unmarshalled *AccountSummary + // to avoid repetitive serialization. + event[utils.AccountSummary] = &as + + return as.FieldAsInterface(fldPath) +} + +// GetBalanceWithID returns a Balance given balance type and balance ID +func (acc *Account) GetBalanceWithID(blcType, blcID string) (blc *Balance) { + for _, blc = range acc.BalanceMap[blcType] { + if blc.ID == blcID { + return + } + } + return nil +} + +// FindBalanceByID searches through all balance types for a balance with the +// specified ID and returns it alongside its type. +func (acc *Account) FindBalanceByID(balanceID string) (blnc *Balance, blncType string) { + for balanceType, balances := range acc.BalanceMap { + for _, balance := range balances { + if balance.ID == balanceID { + return balance, balanceType + } + } + } + return nil, "" +} + func (acc *Account) String() string { return utils.ToJSON(acc) } diff --git a/engine/dynamicdp.go b/engine/dynamicdp.go index 293a2ddb5..17cfddba5 100644 --- a/engine/dynamicdp.go +++ b/engine/dynamicdp.go @@ -19,8 +19,8 @@ along with this program. If not, see package engine import ( - "encoding/json" "fmt" + "slices" "github.com/nyaruka/phonenumbers" @@ -73,39 +73,19 @@ func (dDP *dynamicDP) FieldAsInterface(fldPath []string) (val any, err error) { return nil, utils.ErrNotFound } - // Parsing deeper than the first level in *req.CostDetails requires it to be - // of type *EventCost. If not, we serialize and deserialize into an *EventCost. - if len(fldPath) > 3 && - fldPath[0] == utils.MetaReq && fldPath[1] == utils.CostDetails { + // Ensure type for supported path elements to allow calling their specific + // FieldAsInterface method. + if len(fldPath) > 3 && fldPath[0] == utils.MetaReq && + slices.Contains([]string{utils.CostDetails, utils.AccountSummary}, fldPath[1]) { if mp, canCast := dDP.initialDP.(utils.MapStorage); canCast { if event, canCast := mp[utils.MetaReq].(map[string]any); canCast { - if cd, has := event[utils.CostDetails]; has { - var cdBytes []byte - switch cd := cd.(type) { - case *EventCost: - // Directly proceed if already *EventCost. - return cd.FieldAsInterface(fldPath[2:]) - case string: - // Convert string to bytes for unmarshalling - // if it's a serialized *EventCost. - cdBytes = []byte(cd) - default: - // Marshal non-string types to JSON bytes - // for unmarshalling into *EventCost. - cdBytes, err = json.Marshal(cd) - if err != nil { - return nil, err - } + if field, has := event[fldPath[1]]; has { + switch fldPath[1] { + case utils.CostDetails: + return processEventCostField(fldPath[2:], field, event) + case utils.AccountSummary: + return processAccountSummaryField(fldPath[2:], field, event) } - var ec EventCost - if err = json.Unmarshal(cdBytes, &ec); err != nil { - return nil, err - } - - // Update CostDetails with the unmarshalled *EventCost - // to avoid repetitive serialization. - event[utils.CostDetails] = &ec - return ec.FieldAsInterface(fldPath[2:]) } } } diff --git a/engine/eventcost.go b/engine/eventcost.go index bd1abda49..a84bb2466 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "encoding/json" "errors" "fmt" "slices" @@ -1216,3 +1217,35 @@ func (ec *EventCost) Remove(fldPath []string) error { func (ec *EventCost) GetKeys(nested bool, nesteedLimit int, prefix string) []string { return nil } + +// processEventCostField ensures cd is an EventCost and calls FieldAsInterface on it. +func processEventCostField(fldPath []string, cd any, event map[string]any) (any, error) { + var err error + var cdBytes []byte + switch cd := cd.(type) { + case *EventCost: + // Directly proceed if already *EventCost. + return cd.FieldAsInterface(fldPath) + case string: + // Convert string to bytes for unmarshalling + // if it's a serialized *EventCost. + cdBytes = []byte(cd) + default: + // Marshal non-string types to JSON bytes + // for unmarshalling into *EventCost. + cdBytes, err = json.Marshal(cd) + if err != nil { + return nil, err + } + } + var ec EventCost + if err = json.Unmarshal(cdBytes, &ec); err != nil { + return nil, err + } + + // Update CostDetails with the unmarshalled *EventCost + // to avoid repetitive serialization. + event[utils.CostDetails] = &ec + + return ec.FieldAsInterface(fldPath) +} diff --git a/general_tests/ees_it_test.go b/general_tests/ees_it_test.go index 764e3c41e..c1f27723e 100644 --- a/general_tests/ees_it_test.go +++ b/general_tests/ees_it_test.go @@ -147,6 +147,8 @@ func TestEEsExportEventChanges(t *testing.T) { FilterIDs: []string{ "*string:~*req.CostDetails.Charges[0].Increments[0].Accounting.Balance.ID:BALANCE_TEST", "*string:~*req.CostDetails.Charges[0].Increments[0].Accounting.Balance.Type:*voice", + "*string:~*req.AccountSummary.BalanceSummaries.BALANCE_TEST.ID:BALANCE_TEST", + "*string:~*req.AccountSummary.BalanceSummaries.BALANCE_TEST.Type:*voice", }, Path: "*req.BalanceFound", Type: utils.MetaVariable, @@ -202,6 +204,15 @@ func TestEEsExportEventChanges(t *testing.T) { }, }, }, + utils.AccountSummary: &engine.AccountSummary{ + BalanceSummaries: engine.BalanceSummaries{ + { + ID: "BALANCE_TEST", + Type: utils.MetaVoice, + UUID: "123456", + }, + }, + }, }, }, }