Ensure AccountSummary is of *AccountSummary type when parsing ddp

This commit is contained in:
ionutboangiu
2024-03-06 12:05:48 -05:00
committed by Dan Christian Bogos
parent cdcf55f971
commit 41b9b719d5
4 changed files with 113 additions and 55 deletions

View File

@@ -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)
}

View File

@@ -19,8 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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:])
}
}
}

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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)
}

View File

@@ -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",
},
},
},
},
},
}