NavigableMap should not analyze values internally to keep flexibility and speed

This commit is contained in:
DanB
2018-07-03 17:01:35 +02:00
parent 60a12cd1d8
commit 40c7699ee4
10 changed files with 223 additions and 147 deletions

View File

@@ -116,9 +116,7 @@ func (ar *AgentRequest) AsNavigableMap(tplFlds []*config.CfgCdrField) (
if err != nil {
return nil, err
}
nM.Set(
&engine.NMItem{Path: strings.Split(tplFld.FieldId,
utils.HIERARCHY_SEP), Data: out}, true)
nM.Set(strings.Split(tplFld.FieldId, utils.HIERARCHY_SEP), out, true)
}
return
}

View File

@@ -36,15 +36,15 @@ func TestAgReqAsNavigableMap(t *testing.T) {
agReq := newAgentRequest(nil, nil,
"cgrates.org", filterS)
// populate request, emulating the way will be done in HTTPAgent
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.CGRID},
Data: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String())}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.ToR}, Data: utils.VOICE}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.Account}, Data: "1001"}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.Destination}, Data: "1002"}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.AnswerTime},
Data: time.Date(2013, 12, 30, 15, 0, 1, 0, time.UTC)}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.RequestType}, Data: utils.META_PREPAID}, false)
agReq.CGRRequest.Set(&engine.NMItem{Path: []string{utils.Usage}, Data: time.Duration(3 * time.Minute)}, false)
agReq.CGRRequest.Set([]string{utils.CGRID},
utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), false)
agReq.CGRRequest.Set([]string{utils.ToR}, utils.VOICE, false)
agReq.CGRRequest.Set([]string{utils.Account}, "1001", false)
agReq.CGRRequest.Set([]string{utils.Destination}, "1002", false)
agReq.CGRRequest.Set([]string{utils.AnswerTime},
time.Date(2013, 12, 30, 15, 0, 1, 0, time.UTC), false)
agReq.CGRRequest.Set([]string{utils.RequestType}, utils.META_PREPAID, false)
agReq.CGRRequest.Set([]string{utils.Usage}, time.Duration(3*time.Minute), false)
cgrRply := map[string]interface{}{
utils.CapAttributes: map[string]interface{}{
@@ -99,12 +99,12 @@ func TestAgReqAsNavigableMap(t *testing.T) {
"*cgrReply>Error", utils.INFIELD_SEP)},
}
eMp := engine.NewNavigableMap(nil)
eMp.Set(&engine.NMItem{Path: []string{utils.Tenant}, Data: "cgrates.org"}, true)
eMp.Set(&engine.NMItem{Path: []string{utils.Account}, Data: "1001"}, true)
eMp.Set(&engine.NMItem{Path: []string{utils.Destination}, Data: "1002"}, true)
eMp.Set(&engine.NMItem{Path: []string{"RequestedUsage"}, Data: "180"}, true)
eMp.Set(&engine.NMItem{Path: []string{"PaypalAccount"}, Data: "cgrates@paypal.com"}, true)
eMp.Set(&engine.NMItem{Path: []string{"MaxUsage"}, Data: "120"}, true)
eMp.Set([]string{utils.Tenant}, "cgrates.org", true)
eMp.Set([]string{utils.Account}, "1001", true)
eMp.Set([]string{utils.Destination}, "1002", true)
eMp.Set([]string{"RequestedUsage"}, "180", true)
eMp.Set([]string{"PaypalAccount"}, "cgrates@paypal.com", true)
eMp.Set([]string{"MaxUsage"}, "120", true)
if mpOut, err := agReq.AsNavigableMap(tplFlds); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eMp, mpOut) {

View File

@@ -211,8 +211,7 @@ func (ha *HTTPAgent) processRequest(reqProcessor *config.HttpAgntProcCfg,
var rplyCDRs string
if err = ha.sessionS.Call(utils.SessionSv1ProcessCDR,
*cgrEv, &rplyCDRs); err != nil {
agReq.CGRReply.Set(
&engine.NMItem{Path: []string{utils.Error}, Data: err.Error()}, false)
agReq.CGRReply.Set([]string{utils.Error}, err.Error(), false)
}
}
if nM, err := agReq.AsNavigableMap(reqProcessor.ReplyFields); err != nil {

View File

@@ -67,7 +67,7 @@ func (hU *httpUrlDP) FieldAsInterface(fldPath []string) (data interface{}, err e
}
err = nil // cancel previous err
data = hU.req.FormValue(fldPath[0])
hU.cache.Set(&engine.NMItem{Path: fldPath, Data: data}, false)
hU.cache.Set(fldPath, data, false)
return
}

View File

@@ -411,6 +411,6 @@ func NewCGRReply(rply engine.NavigableMapper,
if err != nil {
return nil, err
}
mp.Set(&engine.NMItem{Path: []string{utils.Error}, Data: ""}, false) // enforce empty error
mp.Set([]string{utils.Error}, "", false) // enforce empty error
return mp, nil
}

View File

@@ -450,7 +450,7 @@ func TestNewCGRReply(t *testing.T) {
},
}
eCgrRply = engine.NewNavigableMap(ev)
eCgrRply.Set(&engine.NMItem{Path: []string{utils.Error}, Data: ""}, false)
eCgrRply.Set([]string{utils.Error}, "", false)
if rpl, err := NewCGRReply(engine.NavigableMapper(ev), nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCgrRply, rpl) {

View File

@@ -34,6 +34,9 @@ func NewCfgCdrFieldFromCdrFieldJsonCfg(jsnCfgFld *CdrFieldJsonCfg) (*CfgCdrField
if jsnCfgFld.Field_id != nil {
cfgFld.FieldId = *jsnCfgFld.Field_id
}
if jsnCfgFld.Attribute_id != nil {
cfgFld.AttributeID = *jsnCfgFld.Attribute_id
}
if jsnCfgFld.Handler_id != nil {
cfgFld.HandlerId = *jsnCfgFld.Handler_id
}
@@ -93,9 +96,10 @@ func NewCfgCdrFieldFromCdrFieldJsonCfg(jsnCfgFld *CdrFieldJsonCfg) (*CfgCdrField
}
type CfgCdrField struct {
Tag string // Identifier for the administrator
Type string // Type of field
FieldId string // Field identifier
Tag string // Identifier for the administrator
Type string // Type of field
FieldId string // Field identifier
AttributeID string
Filters []string // list of filter profiles
HandlerId string
Value utils.RSRFields

View File

@@ -145,6 +145,7 @@ type CdrFieldJsonCfg struct {
Tag *string
Type *string
Field_id *string
Attribute_id *string
Handler_id *string
Value *string
Append *bool

View File

@@ -22,7 +22,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"strings"
"reflect"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
@@ -57,11 +57,11 @@ type NavigableMap struct {
}
// Add will add items into NavigableMap populating also order
func (nM *NavigableMap) Set(itm *NMItem, ordered bool) {
func (nM *NavigableMap) Set(path []string, data interface{}, ordered bool) {
mp := nM.data
for i, spath := range itm.Path {
if i == len(itm.Path)-1 { // last path
mp[spath] = itm
for i, spath := range path {
if i == len(path)-1 { // last path
mp[spath] = data
return
}
if _, has := mp[spath]; !has {
@@ -70,7 +70,7 @@ func (nM *NavigableMap) Set(itm *NMItem, ordered bool) {
mp = mp[spath].(map[string]interface{}) // so we can check further down
}
if ordered {
nM.order = append(nM.order, itm.Path)
nM.order = append(nM.order, path)
}
}
@@ -85,30 +85,27 @@ func (nM *NavigableMap) FieldAsInterface(fldPath []string) (fldVal interface{},
var canCast bool
for i, spath := range fldPath {
if i == lenPath-1 { // lastElement
itmIface, has := lastMp[spath]
var has bool
fldVal, has = lastMp[spath]
if !has {
return nil, utils.ErrNotFound
}
if itm, cast := itmIface.(*NMItem); cast {
return itm.Data, nil
}
return itmIface, nil
} else {
elmnt, has := lastMp[spath]
if !has {
err = fmt.Errorf("no map at path: <%s>", spath)
return
}
elmnt, has := lastMp[spath]
if !has {
err = fmt.Errorf("no map at path: <%s>", spath)
return
}
lastMp, canCast = elmnt.(map[string]interface{})
if !canCast {
lastMpNM, canCast := elmnt.(*NavigableMap) // attempt to cast into NavigableMap
if !canCast {
err = fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
elmnt, elmnt, spath)
return
}
lastMp, canCast = elmnt.(map[string]interface{})
if !canCast {
lastMpNM, canCast := elmnt.(*NavigableMap) // attempt to cast into NavigableMap
if !canCast {
err = fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}",
elmnt, elmnt, spath)
return
}
lastMp = lastMpNM.data
}
lastMp = lastMpNM.data
}
}
err = errors.New("end of function")
@@ -140,43 +137,38 @@ func (nM *NavigableMap) AsMapStringInterface() map[string]interface{} {
}
// indexMapElements will recursively go through map and index the element paths into elmns
func indexMapElements(mp map[string]interface{}, path []string, elms *[]*NMItem) {
func indexMapElements(mp map[string]interface{}, path []string, vals *[]interface{}) {
for k, v := range mp {
vPath := append(path, k)
if mpIface, isMap := v.(map[string]interface{}); isMap {
indexMapElements(mpIface, vPath, elms)
indexMapElements(mpIface, vPath, vals)
continue
}
var elmsOut []*NMItem
if nMItem, isNMItem := v.(*NMItem); isNMItem {
elmsOut = append(*elms, nMItem)
} else {
elmsOut = append(*elms, &NMItem{Path: vPath, Data: v})
}
*elms = elmsOut
valsOut := append(*vals, v)
*vals = valsOut
}
}
// Items returns the items in map, ordered by order information
func (nM *NavigableMap) Items() (itms []*NMItem) {
// Values returns the values in map, ordered by order information
func (nM *NavigableMap) Values() (vals []interface{}) {
if len(nM.data) == 0 {
return
}
if len(nM.order) == 0 {
indexMapElements(nM.data, []string{}, &itms)
indexMapElements(nM.data, []string{}, &vals)
return
}
itms = make([]*NMItem, len(nM.order))
vals = make([]interface{}, len(nM.order))
for i, path := range nM.order {
val, _ := nM.FieldAsInterface(path)
itms[i] = &NMItem{Data: val, Path: path}
vals[i] = val
}
return
}
// AsNavigableMap implements both NavigableMapper as well as DataProvider interfaces
func (nM *NavigableMap) AsNavigableMap(tpl []*config.CfgCdrField) (oNM *NavigableMap, err error) {
func (nM *NavigableMap) AsNavigableMap(
tpl []*config.CfgCdrField) (oNM *NavigableMap, err error) {
return nil, utils.ErrNotImplemented
}
@@ -193,32 +185,67 @@ func (nM *NavigableMap) Merge(nM2 *NavigableMap) {
}
}
// addNMItemToTokens will add a NMItem to tockens
// if itm.Config.IsAttribute the item data is added as Attribute to lastElmnt
// tokens and lastElmntIdx are overloaded on each function call
func addNMItemToTokens(tokens *[]xml.Token, lastElmntIdx *int, itm *NMItem,
lastPath []string) (err error) {
strVal, canCast := utils.CastFieldIfToString(itm.Data)
if !canCast {
return fmt.Errorf("cannot cast field: %s to string", utils.ToJSON(itm.Data))
}
if *lastElmntIdx == -1 || len(itm.Path) <= 1 ||
!reflect.DeepEqual(itm.Path[:len(itm.Path)-1], lastPath) {
charData := []byte("")
if itm.Config == nil ||
itm.Config.AttributeID == "" { // not attribute but value
charData = []byte(strVal)
}
var elmLocal string
if len(itm.Path) != 0 {
elmLocal = itm.Path[len(itm.Path)-1]
}
t := xml.StartElement{Name: xml.Name{Local: elmLocal}}
*lastElmntIdx = len(*tokens)
tOut := append(*tokens, t, xml.CharData(charData), xml.EndElement{t.Name})
*tokens = tOut
}
if itm.Config != nil && itm.Config.AttributeID != "" {
tkns := *tokens
lstElm := tkns[*lastElmntIdx].(xml.StartElement)
lstElm.Attr = append(lstElm.Attr,
xml.Attr{xml.Name{Local: itm.Config.AttributeID}, strVal})
}
return
}
// MarshalXML implements xml.Marshaler
func (nM *NavigableMap) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
tokens := []xml.Token{start}
for _, itm := range nM.Items() {
strVal, canCast := utils.CastFieldIfToString(itm.Data)
if !canCast {
return fmt.Errorf("cannot cast field: %s to string", utils.ToJSON(itm.Data))
var prevItem *NMItem
lastElmntIdx := utils.IntPointer(-1)
for _, itm := range nM.Values() {
nmItems, isNMItems := itm.([]*NMItem)
if !isNMItems {
return fmt.Errorf("map value: <%+v> not []*NMItem", nmItems)
}
var lastPath []string
if prevItem != nil {
lastPath = prevItem.Path
}
for _, itm := range nmItems {
if err = addNMItemToTokens(&tokens, lastElmntIdx, itm, lastPath); err != nil {
return
}
}
t := xml.StartElement{Name: xml.Name{"", strings.Join(itm.Path, ">")}}
tokens = append(tokens, t, xml.CharData([]byte(strVal)), xml.EndElement{t.Name})
}
tokens = append(tokens, xml.EndElement{start.Name})
//fmt.Printf("## OUT: %s\n", utils.ToJSON(tokens))
for _, t := range tokens {
err := e.EncodeToken(t)
if err != nil {
if err = e.EncodeToken(t); err != nil {
return err
}
}
// flush to ensure tokens are written
return e.Flush()
if err != nil {
return err
}
return nil
}

View File

@@ -18,11 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"encoding/xml"
"errors"
//"fmt"
"reflect"
"strings"
"testing"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -148,36 +151,32 @@ func TestNavMapAdd(t *testing.T) {
nM := NewNavigableMap(nil)
path := []string{"FistLever2", "SecondLevel2", "Field2"}
data := "Value2"
nM.Set(&NMItem{Path: path, Data: data}, true)
nM.Set(path, data, true)
path = []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}
data = "Val1"
nM.Set(&NMItem{Path: path, Data: data}, true)
nM.Set(path, data, true)
path = []string{"FistLever2", "Field3"}
data = "Value3"
nM.Set(&NMItem{Path: path, Data: data}, true)
nM.Set(path, data, true)
path = []string{"Field4"}
data = "Val4"
nM.Set(&NMItem{Path: path, Data: data}, true)
nM.Set(path, data, true)
eNavMap := NavigableMap{
data: map[string]interface{}{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": &NMItem{Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1"},
"Fld1": "Val1",
},
},
},
"FistLever2": map[string]interface{}{
"SecondLevel2": map[string]interface{}{
"Field2": &NMItem{Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2"},
"Field2": "Value2",
},
"Field3": &NMItem{Path: []string{"FistLever2", "Field3"},
Data: "Value3"},
"Field3": "Value3",
},
"Field4": &NMItem{Path: []string{"Field4"},
Data: "Val4"},
"Field4": "Val4",
},
}
if !reflect.DeepEqual(nM.data, eNavMap.data) {
@@ -194,42 +193,38 @@ func TestNavMapAdd2(t *testing.T) {
nM := NewNavigableMap(nil)
path := []string{"FistLever2", "SecondLevel2", "Field2"}
data := 123
nM.Set(&NMItem{Path: path, Data: data}, true)
nM.Set(path, data, true)
path = []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}
data1 := 123.123
nM.Set(&NMItem{Path: path, Data: data1}, true)
nM.Set(path, data1, true)
path = []string{"FistLever2", "Field3"}
data2 := "Value3"
nM.Set(&NMItem{Path: path, Data: data2}, true)
nM.Set(path, data2, true)
path = []string{"Field4"}
data3 := &testStruct{
Item1: "Ten",
Item2: 10,
}
nM.Set(&NMItem{Path: path, Data: data3}, true)
nM.Set(path, data3, true)
eNavMap := NavigableMap{
data: map[string]interface{}{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": &NMItem{Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: 123.123},
"Fld1": 123.123,
},
},
},
"FistLever2": map[string]interface{}{
"SecondLevel2": map[string]interface{}{
"Field2": &NMItem{Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: 123},
"Field2": 123,
},
"Field3": &NMItem{Path: []string{"FistLever2", "Field3"},
Data: "Value3"},
"Field3": "Value3",
},
"Field4": &testStruct{
Item1: "Ten",
Item2: 10,
},
"Field4": &NMItem{Path: []string{"Field4"},
Data: &testStruct{
Item1: "Ten",
Item2: 10,
}},
},
}
if !reflect.DeepEqual(nM.data, eNavMap.data) {
@@ -273,8 +268,9 @@ func TestNavMapItems(t *testing.T) {
Data: "Val4",
},
}
if !reflect.DeepEqual(len(nM.Items()), len(eItems)) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eItems), utils.ToJSON(nM.Items()))
if vals := nM.Values(); !reflect.DeepEqual(len(vals), len(eItems)) {
t.Errorf("Expecting: %+v, received: %+v",
utils.ToJSON(eItems), utils.ToJSON(vals))
}
}
@@ -320,8 +316,9 @@ func TestNavMapItems2(t *testing.T) {
},
},
}
if !reflect.DeepEqual(len(nM.Items()), len(eItems)) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eItems), utils.ToJSON(nM.Items()))
if vals := nM.Values(); !reflect.DeepEqual(len(vals), len(eItems)) {
t.Errorf("Expecting: %+v, received: %+v",
utils.ToJSON(eItems), utils.ToJSON(vals))
}
}
@@ -330,17 +327,29 @@ func TestNavMapOrder(t *testing.T) {
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": "Val1",
"Fld1": &NMItem{
Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1",
},
},
},
},
"FistLever2": map[string]interface{}{
"SecondLevel2": map[string]interface{}{
"Field2": "Value2",
"Field2": &NMItem{
Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2",
},
},
"Field3": &NMItem{
Path: []string{"FistLever2", "Field3"},
Data: "Value3",
},
"Field3": "Value3",
},
"Field4": "Val4",
"Field4": &NMItem{
Path: []string{"Field4"},
Data: "Val4",
},
}
order := [][]string{
[]string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
@@ -350,7 +359,7 @@ func TestNavMapOrder(t *testing.T) {
}
nM := NewNavigableMap(myData)
nM.order = order
eItems := []*NMItem{
eItems := []interface{}{
&NMItem{
Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1",
@@ -368,8 +377,9 @@ func TestNavMapOrder(t *testing.T) {
Data: "Val4",
},
}
if !reflect.DeepEqual(nM.Items(), eItems) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eItems), utils.ToJSON(nM.Items()))
if vals := nM.Values(); !reflect.DeepEqual(vals, eItems) {
t.Errorf("Expecting: %+v, received: %+v",
utils.ToJSON(eItems), utils.ToJSON(vals))
}
}
@@ -378,17 +388,29 @@ func TestNavMapOrder2(t *testing.T) {
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": "Val1",
"Fld1": &NMItem{
Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1",
},
},
},
},
"FistLever2": map[string]interface{}{
"SecondLevel2": map[string]interface{}{
"Field2": "Value2",
"Field2": &NMItem{
Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2",
},
},
"Field3": &NMItem{
Path: []string{"FistLever2", "Field3"},
Data: "Value3",
},
"Field3": "Value3",
},
"Field4": "Val4",
"Field4": &NMItem{
Path: []string{"Field4"},
Data: "Val4",
},
}
order := [][]string{
[]string{"FistLever2", "SecondLevel2", "Field2"},
@@ -398,7 +420,7 @@ func TestNavMapOrder2(t *testing.T) {
}
nM := NewNavigableMap(myData)
nM.order = order
eItems := []*NMItem{
eItems := []interface{}{
&NMItem{
Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2",
@@ -416,13 +438,14 @@ func TestNavMapOrder2(t *testing.T) {
Data: "Val1",
},
}
if !reflect.DeepEqual(nM.Items(), eItems) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eItems), utils.ToJSON(nM.Items()))
if vals := nM.Values(); !reflect.DeepEqual(eItems, vals) {
t.Errorf("Expecting: %+v, received: %+v",
utils.ToJSON(eItems), utils.ToJSON(vals))
}
}
func TestNavMapIndexMapElementes(t *testing.T) {
var elmsOut []*NMItem
var elmsOut []interface{}
ifaceMap := map[string]interface{}{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
@@ -439,24 +462,7 @@ func TestNavMapIndexMapElementes(t *testing.T) {
},
"Field4": "Val4",
}
eItems := []*NMItem{
&NMItem{
Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1",
},
&NMItem{
Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2",
},
&NMItem{
Path: []string{"FistLever2", "Field3"},
Data: "Value3",
},
&NMItem{
Path: []string{"Field4"},
Data: "Val4",
},
}
eItems := []interface{}{"Val1", "Value2", "Value3", "Val4"}
indexMapElements(ifaceMap, []string{}, &elmsOut)
if !reflect.DeepEqual(len(elmsOut), len(eItems)) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eItems), utils.ToJSON(elmsOut))
@@ -486,3 +492,44 @@ func TestNavMapString(t *testing.T) {
t.Errorf("Expecting: %+v, received: %+v", eStr, nM.String())
}
}
func TestNavMapMarshalXML(t *testing.T) {
nm := &NavigableMap{
data: map[string]interface{}{
"FirstLevel": map[string]interface{}{
"SecondLevel": map[string]interface{}{
"ThirdLevel": map[string]interface{}{
"Fld1": []*NMItem{
&NMItem{Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
Data: "Val1"}},
},
},
},
"FistLever2": map[string]interface{}{
"SecondLevel2": map[string]interface{}{
"Field2": []*NMItem{
&NMItem{Path: []string{"FistLever2", "SecondLevel2", "Field2"},
Data: "Value2",
Config: &config.CfgCdrField{Tag: "AttributeTest", AttributeID: "attribute1"}}},
},
"Field3": []*NMItem{
&NMItem{Path: []string{"FistLever2", "Field3"},
Data: "Value3"}},
},
"Field4": []*NMItem{
&NMItem{Path: []string{"Field4"},
Data: "Val4"}},
},
order: [][]string{
[]string{"FistLever2", "SecondLevel2", "Field2"},
[]string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"},
[]string{"FistLever2", "Field3"},
[]string{"Field4"},
},
}
if output, err := xml.MarshalIndent(nm, "", " "); err != nil {
t.Error(err)
} else if !reflect.DeepEqual([]byte(""), output) {
//fmt.Printf("received output: <%s>\n", output)
}
}