diff --git a/agents/agentreq_test.go b/agents/agentreq_test.go index 05b7f4d4e..199bc74a0 100644 --- a/agents/agentreq_test.go +++ b/agents/agentreq_test.go @@ -145,10 +145,10 @@ func TestAgentRequestSetFields(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() dm := engine.NewDataManager(engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items), config.CgrConfig().CacheCfg(), nil) - ar := NewAgentRequest(config.NewNavigableMap(req), nil, + ar := NewAgentRequest(utils.MapStorage(req), nil, nil, nil, nil, config.NewRSRParsersMustCompile("", false, utils.NestingSep), "cgrates.org", "", engine.NewFilterS(cfg, nil, dm), - config.NewNavigableMap(req), config.NewNavigableMap(req)) + utils.MapStorage(req), utils.MapStorage(req)) input := []*config.FCTemplate{} if err := ar.SetFields(input); err != nil { t.Error(err) diff --git a/agents/diamagent_test.go b/agents/diamagent_test.go index 01f6ce046..2ba06c95d 100644 --- a/agents/diamagent_test.go +++ b/agents/diamagent_test.go @@ -62,12 +62,12 @@ func TestProcessRequest(t *testing.T) { cgrRplyNM := utils.NavigableMap2{} rply := utils.NewOrderedNavigableMap() - diamDP := config.NewNavigableMap(map[string]interface{}{ + diamDP := utils.MapStorage{ "SessionId": "123456", "Account": "1001", "Destination": "1003", "Usage": 10 * time.Second, - }) + } reqProcessor := &config.RequestProcessor{ ID: "Default", Tenant: config.NewRSRParsersMustCompile("cgrates.org", true, utils.INFIELD_SEP), diff --git a/agents/libdiam.go b/agents/libdiam.go index 522750f36..e73ca5fd7 100644 --- a/agents/libdiam.go +++ b/agents/libdiam.go @@ -289,7 +289,7 @@ func writeOnConn(c diam.Conn, m *diam.Message) (err error) { // newDADataProvider constructs a DataProvider for a diameter message func newDADataProvider(c diam.Conn, m *diam.Message) utils.DataProvider { - return &diameterDP{c: c, m: m, cache: config.NewNavigableMap(nil)} + return &diameterDP{c: c, m: m, cache: utils.MapStorage{}} } @@ -298,7 +298,7 @@ func newDADataProvider(c diam.Conn, m *diam.Message) utils.DataProvider { type diameterDP struct { c diam.Conn m *diam.Message - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -408,7 +408,7 @@ func (dP *diameterDP) FieldAsInterface(fldPath []string) (data interface{}, err if data, err = diamAVPAsIface(avps[slectedIdx]); err != nil { return nil, err } - dP.cache.Set(fldPath, data, false, false) + dP.cache.Set(fldPath, data) return } diff --git a/agents/libdns.go b/agents/libdns.go index 147a914dc..629b05a0d 100644 --- a/agents/libdns.go +++ b/agents/libdns.go @@ -62,7 +62,7 @@ func domainNameFromNAPTR(name string) (dName string) { func newDNSDataProvider(req *dns.Msg, w dns.ResponseWriter) utils.DataProvider { return &dnsDP{req: req, w: w, - cache: config.NewNavigableMap(nil)} + cache: utils.MapStorage{}} } // dnsDP implements engien.DataProvider, serving as dns.Msg decoder @@ -70,7 +70,7 @@ func newDNSDataProvider(req *dns.Msg, type dnsDP struct { req *dns.Msg w dns.ResponseWriter - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -109,7 +109,7 @@ func (dP *dnsDP) FieldAsInterface(fldPath []string) (data interface{}, err error if len(dP.req.Question) != 0 { data = dP.req.Question[0] } - dP.cache.Set(fldPath, data, false, false) + dP.cache.Set(fldPath, data) return } diff --git a/agents/libhttpagent.go b/agents/libhttpagent.go index be5c44160..18af2e458 100644 --- a/agents/libhttpagent.go +++ b/agents/libhttpagent.go @@ -48,7 +48,7 @@ func newHADataProvider(reqPayload string, } func newHTTPUrlDP(req *http.Request) (dP utils.DataProvider, err error) { - dP = &httpUrlDP{req: req, cache: config.NewNavigableMap(nil)} + dP = &httpUrlDP{req: req, cache: utils.MapStorage{}} return } @@ -56,7 +56,7 @@ func newHTTPUrlDP(req *http.Request) (dP utils.DataProvider, err error) { // decoded data is only searched once and cached type httpUrlDP struct { req *http.Request - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -80,7 +80,7 @@ func (hU *httpUrlDP) FieldAsInterface(fldPath []string) (data interface{}, err e return // data found in cache } data = hU.req.FormValue(fldPath[0]) - hU.cache.Set(fldPath, data, false, false) + hU.cache.Set(fldPath, data) return } @@ -109,14 +109,14 @@ func newHTTPXmlDP(req *http.Request) (dP utils.DataProvider, err error) { if err != nil { return nil, err } - dP = &httpXmlDP{xmlDoc: doc, cache: config.NewNavigableMap(nil), addr: req.RemoteAddr} + dP = &httpXmlDP{xmlDoc: doc, cache: utils.MapStorage{}, addr: req.RemoteAddr} return } // httpXmlDP implements utils.DataProvider, serving as xml data decoder // decoded data is only searched once and cached type httpXmlDP struct { - cache *config.NavigableMap + cache utils.MapStorage xmlDoc *xmlquery.Node addr string } @@ -168,7 +168,7 @@ func (hU *httpXmlDP) FieldAsInterface(fldPath []string) (data interface{}, err e } //add the content in data and cache it data = elmnt.InnerText() - hU.cache.Set(fldPath, data, false, false) + hU.cache.Set(fldPath, data) return } diff --git a/agents/librad.go b/agents/librad.go index 41b51bdda..bb740f850 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -62,7 +62,7 @@ func radReplyAppendAttributes(reply *radigo.Packet, rplNM *utils.OrderedNavigabl // newRADataProvider constructs a DataProvider func newRADataProvider(req *radigo.Packet) (dP utils.DataProvider) { - dP = &radiusDP{req: req, cache: config.NewNavigableMap(nil)} + dP = &radiusDP{req: req, cache: utils.MapStorage{}} return } @@ -70,7 +70,7 @@ func newRADataProvider(req *radigo.Packet) (dP utils.DataProvider) { // decoded data is only searched once and cached type radiusDP struct { req *radigo.Packet - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -95,7 +95,7 @@ func (pk *radiusDP) FieldAsInterface(fldPath []string) (data interface{}, err er if len(pk.req.AttributesWithName(fldPath[0], "")) != 0 { data = pk.req.AttributesWithName(fldPath[0], "")[0].GetStringValue() } - pk.cache.Set(fldPath, data, false, false) + pk.cache.Set(fldPath, data) return } diff --git a/agents/sipagent.go b/agents/sipagent.go index 4ef7209d0..4aafacf20 100644 --- a/agents/sipagent.go +++ b/agents/sipagent.go @@ -227,7 +227,7 @@ func (sa *SIPAgent) handleMessage(sipMessage sipingo.Message, remoteHost string) for k, v := range sipMessage { sipMessageIface[k] = v } - dp := config.NewNavigableMap(sipMessageIface) + dp := utils.MapStorage(sipMessageIface) var processed bool cgrRplyNM := utils.NavigableMap2{} rplyNM := utils.NewOrderedNavigableMap() diff --git a/config/fwvdp.go b/config/fwvdp.go index 1ab306333..61e332b23 100644 --- a/config/fwvdp.go +++ b/config/fwvdp.go @@ -29,14 +29,14 @@ import ( // NewfwvProvider constructs a utils.DataProvider func NewFWVProvider(record string) (dP utils.DataProvider) { - dP = &FWVProvider{req: record, cache: NewNavigableMap(nil)} + dP = &FWVProvider{req: record, cache: utils.MapStorage{}} return } // fwvProvider implements engine.utils.DataProvider so we can pass it to filters type FWVProvider struct { req string - cache *NavigableMap + cache utils.MapStorage } // String is part of engine.utils.DataProvider interface @@ -75,7 +75,7 @@ func (fP *FWVProvider) FieldAsInterface(fldPath []string) (data interface{}, err return "", fmt.Errorf("FinalIndex : %+v is greater than : %+v", finalIndex, len(fP.req)) } data = fP.req[startIndex:finalIndex] - fP.cache.Set(fldPath, data, false, false) + fP.cache.Set(fldPath, data) return } diff --git a/config/navigablemap.go b/config/navigablemap.go index 499f35744..716a3bfe8 100644 --- a/config/navigablemap.go +++ b/config/navigablemap.go @@ -20,11 +20,7 @@ package config import ( "encoding/xml" - "errors" "fmt" - "net" - "reflect" - "strconv" "strings" "time" @@ -83,427 +79,6 @@ func (nmi *NMItem) Len() int { return 0 } -// NewNavigableMap constructs a NavigableMap -func NewNavigableMap(data map[string]interface{}) *NavigableMap { - if data == nil { - data = make(map[string]interface{}) - } - return &NavigableMap{data: data} -} - -// NavigableMap is a map who's values can be navigated via path -// data can be retrieved as ordered -// NavigableMap is not thread safe due to performance demands, could come back if needed -type NavigableMap struct { - data map[string]interface{} // layered map - order [][]string // order of field paths -} - -// Set will set items into NavigableMap populating also order -// apnd parameter allows appending the data if both sides are []*NMItem -func (nM *NavigableMap) Set(path []string, data interface{}, apnd, ordered bool) { - if ordered { - nM.order = append(nM.order, path) - } - mp := nM.data - for i, spath := range path { - if i == len(path)-1 { // last path - oData, has := mp[spath] - if !has || !apnd { // no need to append - mp[spath] = data - return - } - dataItms, isNMItems := data.([]*NMItem) - if !isNMItems { // new data is not items - mp[spath] = data - return - } - oItms, isNMItems := oData.([]*NMItem) - if !isNMItems { // previous data is not items, simply overwrite - mp[spath] = data - return - } - mp[spath] = append(oItms, dataItms...) - return - } - if _, has := mp[spath]; !has { - mp[spath] = make(map[string]interface{}) - } - mp = mp[spath].(map[string]interface{}) // so we can check further down - } -} - -// GetField returns a field in it's original format, without converting from ie. *NMItem -func (nM *NavigableMap) GetField(path []string) (fldVal interface{}, err error) { - lenPath := len(path) - if lenPath == 0 { - return nil, errors.New("empty field path") - } - lastMp := nM.data // last map when layered - for i, spath := range path { - if i == lenPath-1 { // lastElement - return nM.getLastRealItem(lastMp, spath) - } - var dp interface{} - if dp, err = nM.getNextMap(lastMp, spath); err != nil { - return - } - switch mv := dp.(type) { // used for cdr when populating eventCost whitin - case map[string]interface{}: - lastMp = mv - case utils.DataProvider: - return mv.FieldAsInterface(path[i+1:]) - default: - return nil, fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", - dp, dp, spath) - } - } - panic("BUG") // should never make it here -} - -// getLastItem returns the item from the map -// checking if it needs to return the item or an element of him if the item is a slice -func (nM *NavigableMap) getLastRealItem(mp map[string]interface{}, spath string) (val interface{}, err error) { - var idx *int - spath, idx = nM.getIndex(spath) - var has bool - val, has = mp[spath] - if !has { - return nil, utils.ErrNotFound - } - if idx == nil { - return val, nil - } - switch vt := val.(type) { - case []string: - if *idx > len(vt) { - return nil, fmt.Errorf("selector index %d out of range", *idx) - } - return vt[*idx], nil - default: - } - // only if all above fails use reflect: - vr := reflect.ValueOf(val) - if vr.Kind() == reflect.Ptr { - vr = vr.Elem() - } - if vr.Kind() != reflect.Slice && vr.Kind() != reflect.Array { - return nil, fmt.Errorf("selector index used on non slice type(%T)", val) - } - if *idx > vr.Len() { - return nil, fmt.Errorf("selector index %d out of range", *idx) - } - return vr.Index(*idx).Interface(), nil -} - -// FieldAsInterface returns the field value as interface{} for the path specified -// implements utils.DataProvider -// supports spath with selective elements in case of []*NMItem -func (nM *NavigableMap) FieldAsInterface(fldPath []string) (fldVal interface{}, err error) { - lenPath := len(fldPath) - if lenPath == 0 { - return nil, errors.New("empty field path") - } - lastMp := nM.data // last map when layered - for i, spath := range fldPath { - if i == lenPath-1 { // lastElement - return nM.getLastItem(lastMp, spath) - } - var dp interface{} - if dp, err = nM.getNextMap(lastMp, spath); err != nil { - return - } - switch mv := dp.(type) { // used for cdr when populating eventCost whitin - case map[string]interface{}: - lastMp = mv - case utils.DataProvider: - return mv.FieldAsInterface(fldPath[i+1:]) - default: - return nil, fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", - dp, dp, spath) - } - } - err = errors.New("end of function") - return -} - -// getLastItem returns the item from the map -// checking if it needs to return the item or an element of him if the item is a slice -func (nM *NavigableMap) getLastItem(mp map[string]interface{}, spath string) (val interface{}, err error) { - var idx *int - spath, idx = nM.getIndex(spath) - var has bool - val, has = mp[spath] - if !has { - return nil, utils.ErrNotFound - } - if idx == nil { - return val, nil - } - switch vt := val.(type) { - case []string: - if *idx > len(vt) { - return nil, utils.ErrNotFound - // return nil, fmt.Errorf("selector index %d out of range", *idx) - } - return vt[*idx], nil - case []*NMItem: - if *idx > len(vt) { - return nil, utils.ErrNotFound - // return nil, fmt.Errorf("selector index %d out of range", *idx) - } - return vt[*idx].Data, nil - default: - } - // only if all above fails use reflect: - vr := reflect.ValueOf(val) - if vr.Kind() == reflect.Ptr { - vr = vr.Elem() - } - if vr.Kind() != reflect.Slice && vr.Kind() != reflect.Array { - return nil, utils.ErrNotFound - // return nil, fmt.Errorf("selector index used on non slice type(%T)", val) - } - if *idx > vr.Len() { - return nil, utils.ErrNotFound - // return nil, fmt.Errorf("selector index %d out of range", *idx) - } - return vr.Index(*idx).Interface(), nil -} - -// getNextMap returns the next map from the given map -// used only for path parsing -func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (interface{}, error) { - var idx *int - spath, idx = nM.getIndex(spath) - mi, has := mp[spath] - if !has { - return nil, utils.ErrNotFound - } - if idx == nil { - switch mv := mi.(type) { - case map[string]interface{}: - return mv, nil - case *map[string]interface{}: - return *mv, nil - case NavigableMap: - return mv.data, nil - case *NavigableMap: - return mv.data, nil - case utils.DataProvider: // used for cdr when populating eventCost whitin - return mv, nil - default: - } - } else { - switch mv := mi.(type) { - case []interface{}: - // in case we create the map using json and we marshall the value into a map[string]interface{} - // we can have slice of interfaces that is masking a slice of map[string]interface{} - // this is for CostDetails BalanceSummaries - if *idx < len(mv) { - mm := mv[*idx] - switch mmv := mm.(type) { - case map[string]interface{}: - return mmv, nil - case *map[string]interface{}: - return *mmv, nil - case NavigableMap: - return mmv.data, nil - case *NavigableMap: - return mmv.data, nil - default: - } - } - case []map[string]interface{}: - if *idx < len(mv) { - return mv[*idx], nil - } - case []NavigableMap: - if *idx < len(mv) { - return mv[*idx].data, nil - } - case []*NavigableMap: - if *idx < len(mv) { - return mv[*idx].data, nil - } - case []utils.DataProvider: // used for cdr when populating eventCost whitin - if *idx < len(mv) { - return mv[*idx], nil - } - default: - } - return nil, utils.ErrNotFound // xml compatible - } - return nil, fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", - mi, mi, spath) -} - -// getIndex returns the path and index if index present -// path[index]=>path,index -// path=>path,nil -func (nM *NavigableMap) getIndex(spath string) (opath string, idx *int) { - idxStart := strings.Index(spath, utils.IdxStart) - if idxStart == -1 || !strings.HasSuffix(spath, utils.IdxEnd) { - return spath, nil - } - slctr := spath[idxStart+1 : len(spath)-1] - opath = spath[:idxStart] - if strings.HasPrefix(slctr, utils.DynamicDataPrefix) { - return - } - idxVal, err := strconv.Atoi(slctr) - if err != nil { - return spath, nil - } - return opath, &idxVal -} - -// FieldAsString returns the field value as string for the path specified -// implements utils.DataProvider -func (nM *NavigableMap) FieldAsString(fldPath []string) (fldVal string, err error) { - var valIface interface{} - valIface, err = nM.FieldAsInterface(fldPath) - if err != nil { - return - } - return utils.IfaceAsString(valIface), nil -} - -// String is part of engine.utils.DataProvider interface -func (nM *NavigableMap) String() string { - return utils.ToJSON(nM.data) -} - -// RemoteHost is part of engine.utils.DataProvider interface -func (nM *NavigableMap) RemoteHost() net.Addr { - return utils.LocalAddr() -} - -// indexMapElements will recursively go through map and index the element paths into elmns -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, vals) - continue - } - valsOut := append(*vals, v) - *vals = valsOut - } -} - -// 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{}, &vals) - return - } - vals = make([]interface{}, len(nM.order)) - for i, path := range nM.order { - val, _ := nM.FieldAsInterface(path) - vals[i] = val - } - return -} - -// Merge will update nM with values from a second one -func (nM *NavigableMap) Merge(nM2 *NavigableMap) { - if nM2 == nil { - return - } - if len(nM2.order) == 0 { - indexMapPaths(nM2.data, nil, &nM.order) - } - pathIdx := make(map[string]int) // will hold references for last index exported in case of []*NMItem - for _, path := range nM2.order { - val, _ := nM2.FieldAsInterface(path) - if valItms, isItms := val.([]*NMItem); isItms { - pathStr := strings.Join(path, utils.NestingSep) - pathIdx[pathStr]++ - if pathIdx[pathStr] > len(valItms) { - val = valItms[len(valItms)-1:] // slice with only last element in, so we can set it unlimited - } else { - val = []*NMItem{valItms[pathIdx[pathStr]-1]} // set only one item per path - } - } - nM.Set(path, val, true, - (len(nM.order) != 0 || len(nM.data) == 0)) - } - return -} - -// indexMapPaths parses map returning the parsed branchPath, useful when not having order for NavigableMap -func indexMapPaths(mp map[string]interface{}, branchPath []string, parsedPaths *[][]string) { - for k, v := range mp { - if mpIface, isMap := v.(map[string]interface{}); isMap { - indexMapPaths(mpIface, append(branchPath, k), parsedPaths) - continue - } - tmpPaths := append(*parsedPaths, append(branchPath, k)) - *parsedPaths = tmpPaths - } -} - -// AsCGREvent builds a CGREvent considering Time as time.Now() -// and Event as linear map[string]interface{} with joined paths -// treats particular case when the value of map is []*NMItem - used in agents/AgentRequest -func (nM *NavigableMap) AsCGREvent(tnt string, pathSep string) (cgrEv *utils.CGREvent) { - if nM == nil || len(nM.data) == 0 { - return - } - cgrEv = &utils.CGREvent{ - Tenant: tnt, - ID: utils.UUIDSha1Prefix(), - Time: utils.TimePointer(time.Now()), - Event: make(map[string]interface{})} - if len(nM.order) == 0 { - indexMapPaths(nM.data, nil, &nM.order) - } - for _, branchPath := range nM.order { - val, _ := nM.FieldAsInterface(branchPath) - if nmItms, isNMItems := val.([]*NMItem); isNMItems { // special case when we have added multiple items inside a key, used in agents - for _, nmItm := range nmItms { - if nmItm.Config == nil || - nmItm.Config.AttributeID == "" { - val = nmItm.Data // first item which is not an attribute will become the value - break - } - } - } - cgrEv.Event[strings.Join(branchPath, pathSep)] = val - } - return -} - -// AsMapStringIface builds a linear map[string]interface{} with joined paths -// treats particular case when the value of map is []*NMItem - used in agents/AgentRequest -func (nM *NavigableMap) AsMapStringIface(pathSep string) (mp map[string]interface{}) { - if nM == nil || len(nM.data) == 0 { - return - } - mp = make(map[string]interface{}) - if len(nM.order) == 0 { - indexMapPaths(nM.data, nil, &nM.order) - } - for _, branchPath := range nM.order { - val, _ := nM.FieldAsInterface(branchPath) - if nmItms, isNMItems := val.([]*NMItem); isNMItems { // special case when we have added multiple items inside a key, used in agents - for _, nmItm := range nmItms { - if nmItm.Config == nil || - nmItm.Config.AttributeID == "" { - val = nmItm.Data // first item which is not an attribute will become the value - break - } - } - } - mp[strings.Join(branchPath, pathSep)] = val - } - return -} - // XMLElement is specially crafted to be automatically marshalled by encoding/xml type XMLElement struct { XMLName xml.Name @@ -512,222 +87,6 @@ type XMLElement struct { Elements []*XMLElement `xml:"omitempty"` } -// AsXMLElements returns the values as []*XMLElement which can be later marshaled -// considers each value returned by .Values() in the form of []*NMItem, otherwise errors -func (nM *NavigableMap) AsXMLElements() (ents []*XMLElement, err error) { - pathIdx := make(map[string]*XMLElement) // Keep the index of elements based on path - for _, val := range nM.Values() { - nmItms, isNMItems := val.([]*NMItem) - if !isNMItems { - return nil, fmt.Errorf("value: %+v is not []*NMItem", val) - } - for _, nmItm := range nmItms { - if nmItm.Config != nil && nmItm.Config.NewBranch { - pathIdx = make(map[string]*XMLElement) // reset cache so we can start having other elements with same path - } - val := utils.IfaceAsString(nmItm.Data) - var pathCached bool - for i := len(nmItm.Path); i > 0; i-- { - var cachedElm *XMLElement - if cachedElm, pathCached = pathIdx[strings.Join(nmItm.Path[:i], "")]; !pathCached { - continue - } - if i == len(nmItm.Path) { // lastElmnt, overwrite value or add attribute - if nmItm.Config != nil && - nmItm.Config.AttributeID != "" { - cachedElm.Attributes = append(cachedElm.Attributes, - &xml.Attr{xml.Name{Local: nmItm.Config.AttributeID}, val}) - } else { - cachedElm.Value = val - } - break - } - // create elements in reverse order so we can append already created - var newElm *XMLElement - for j := len(nmItm.Path); j > i; j-- { - elm := &XMLElement{XMLName: xml.Name{Local: nmItm.Path[j-1]}} - pathIdx[strings.Join(nmItm.Path[:j], "")] = elm - if newElm == nil { - if nmItm.Config != nil && - nmItm.Config.AttributeID != "" { - elm.Attributes = append(elm.Attributes, - &xml.Attr{xml.Name{Local: nmItm.Config.AttributeID}, val}) - } else { - elm.Value = val - } - newElm = elm // last element - } else { - elm.Elements = append(elm.Elements, newElm) - newElm = elm - } - } - cachedElm.Elements = append(cachedElm.Elements, newElm) - } - if !pathCached { // not an update but new element to be created - var newElm *XMLElement - for i := len(nmItm.Path); i > 0; i-- { - elm := &XMLElement{XMLName: xml.Name{Local: nmItm.Path[i-1]}} - pathIdx[strings.Join(nmItm.Path[:i], "")] = elm - if newElm == nil { // last element, create data inside - if nmItm.Config != nil && - nmItm.Config.AttributeID != "" { - elm.Attributes = append(elm.Attributes, - &xml.Attr{xml.Name{Local: nmItm.Config.AttributeID}, val}) - } else { - elm.Value = val - } - newElm = elm // last element - } else { - elm.Elements = append(elm.Elements, newElm) - newElm = elm - } - } - ents = append(ents, newElm) - } - } - } - return -} - -func getPathFromValue(in reflect.Value, prefix string) (out []string) { - switch in.Kind() { - case reflect.Ptr: - return getPathFromValue(in.Elem(), prefix) - case reflect.Array, reflect.Slice: - prefix = strings.TrimSuffix(prefix, utils.NestingSep) - for i := 0; i < in.Len(); i++ { - pref := fmt.Sprintf("%s[%v]", prefix, i) - out = append(out, pref) - out = append(out, getPathFromValue(in.Index(i), pref+utils.NestingSep)...) - } - case reflect.Map: - iter := reflect.ValueOf(in).MapRange() - for iter.Next() { - pref := prefix + iter.Key().String() - out = append(out, pref) - out = append(out, getPathFromValue(iter.Value(), pref+utils.NestingSep)...) - } - case reflect.Struct: - inType := in.Type() - for i := 0; i < in.NumField(); i++ { - pref := prefix + inType.Field(i).Name - out = append(out, pref) - out = append(out, getPathFromValue(in.Field(i), pref+utils.NestingSep)...) - } - case reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String, reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Interface: - default: - } - return -} - -func getPathFromInterface(in interface{}, prefix string) (out []string) { - switch vin := in.(type) { - case map[string]interface{}: - for k, val := range vin { - pref := prefix + k - out = append(out, pref) - out = append(out, getPathFromInterface(val, pref+utils.NestingSep)...) - } - case []map[string]interface{}: - prefix = strings.TrimSuffix(prefix, utils.NestingSep) - for i, val := range vin { - pref := fmt.Sprintf("%s[%v]", prefix, i) - out = append(out, pref) - out = append(out, getPathFromInterface(val, pref+utils.NestingSep)...) - } - case []interface{}: - prefix = strings.TrimSuffix(prefix, utils.NestingSep) - for i, val := range vin { - pref := fmt.Sprintf("%s[%v]", prefix, i) - out = append(out, pref) - out = append(out, getPathFromInterface(val, pref+utils.NestingSep)...) - } - case []string: - prefix = strings.TrimSuffix(prefix, utils.NestingSep) - for i := range vin { - pref := fmt.Sprintf("%s[%v]", prefix, i) - out = append(out, pref) - } - case nil, int, int32, int64, uint32, uint64, bool, float32, float64, []uint8, time.Duration, time.Time, string: //no path - default: //reflect based - out = getPathFromValue(reflect.ValueOf(vin), prefix) - } - return -} - -// GetKeys returns all posibble keys -func (nM *NavigableMap) GetKeys() (keys []string) { - for k, val := range nM.data { - keys = append(keys, k) - keys = append(keys, getPathFromInterface(val, k+utils.NestingSep)...) - } - return -} - -// Remove will remove the items from the given path -func (nM *NavigableMap) Remove(path []string) { - // if ordered { - // nM.order = append(nM.order, path) - // } - mp := nM.data - for i, spath := range path { - oData, has := mp[spath] - if !has { // no need to remove - return - } - if i == len(path)-1 { // last path - delete(mp, spath) - return - } - defer func(np map[string]interface{}, p string) { - o, has := np[p] - if !has { - return - } - if o == nil { - delete(np, p) - return - } - v, ok := o.(map[string]interface{}) - if !ok { - return - } - if len(v) == 0 { - delete(np, p) - } - }(mp, spath) - mp = oData.(map[string]interface{}) // so we can check further down - } -} - -// RemoveAll will clean the data and the odrder from NavigableMap -func (nM *NavigableMap) RemoveAll() { - nM.data = make(map[string]interface{}) - nM.order = make([][]string, 0) -} - -// GetData returns the map from inside the NavMap without modifying it -func (nM *NavigableMap) GetData() (mp map[string]interface{}) { - mp = make(map[string]interface{}) - if len(nM.order) == 0 { - indexMapPaths(nM.data, nil, &nM.order) - } - for _, branchPath := range nM.order { - val, _ := nM.FieldAsInterface(branchPath) - if nmItms, isNMItems := val.([]*NMItem); isNMItems { // special case when we have added multiple items inside a key, used in agents - for _, nmItm := range nmItms { - if nmItm.Config == nil || - nmItm.Config.AttributeID == "" { - val = nmItm.Data // first item which is not an attribute will become the value - break - } - } - } - mp[strings.Join(branchPath, utils.NestingSep)] = val - } - return -} - // NMAsXMLElements returns the values as []*XMLElement which can be later marshaled // considers each value returned by .Values() in the form of []*NMItem, otherwise errors func NMAsXMLElements(nm *utils.OrderedNavigableMap) (ents []*XMLElement, err error) { diff --git a/config/navigablemap_test.go b/config/navigablemap_test.go index f4f7b4439..75c42c56a 100644 --- a/config/navigablemap_test.go +++ b/config/navigablemap_test.go @@ -19,1171 +19,14 @@ package config import ( "encoding/xml" - "fmt" "math/rand" "reflect" - "sort" "strings" "testing" "github.com/cgrates/cgrates/utils" ) -func TestNavMapGetFieldAsString(t *testing.T) { - nM := &NavigableMap{ - data: map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "AnotherFirstLevel": "ValAnotherFirstLevel", - }, - } - eVal := "Val1" - if strVal, err := nM.FieldAsString( - strings.Split("FirstLevel>SecondLevel>ThirdLevel>Fld1", ">")); err != nil { - t.Error(err) - } else if strVal != eVal { - t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) - } - eVal = "ValAnotherFirstLevel" - if strVal, err := nM.FieldAsString( - strings.Split("AnotherFirstLevel", ">")); err != nil { - t.Error(err) - } else if strVal != eVal { - t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) - } - fPath := "NonExisting>AnotherFirstLevel" - if _, err := nM.FieldAsString(strings.Split(fPath, ">")); err.Error() != - utils.ErrNotFound.Error() { - t.Error(err) - } -} - -type myEv map[string]interface{} - -func (ev myEv) AsNavigableMap() *NavigableMap { - return NewNavigableMap(ev) -} - -func TestNavMapAsNavigableMap(t *testing.T) { - myData := myEv{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": 123.123, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": 123, - }, - "Field3": "Value3", - }, - "Field4": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - } - - eNavMap := NavigableMap{ - data: map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": 123.123, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": 123, - }, - "Field3": "Value3", - }, - "Field4": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - }, - } - - if rcv := myData.AsNavigableMap(); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eNavMap.data, rcv.data) { - t.Errorf("Expecting: %+v, received: %+v", eNavMap.data, rcv.data) - } -} - -func TestNavMapNewNavigableMap(t *testing.T) { - myData := map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": "Value2", - }, - "Field3": "Value3", - }, - "Field4": "Val4", - } - - eNavMap := NavigableMap{ - data: myData, - } - - nM := NewNavigableMap(myData) - if !reflect.DeepEqual(nM.data, eNavMap.data) { - t.Errorf("Expecting: %+v, received: %+v", eNavMap.data, nM.data) - } -} - -func TestNavMapAdd(t *testing.T) { - nM := NewNavigableMap(nil) - path := []string{"FistLever2", "SecondLevel2", "Field2"} - data := "Value2" - nM.Set(path, data, false, true) - path = []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"} - data = "Val1" - nM.Set(path, data, false, true) - path = []string{"FistLever2", "Field3"} - data = "Value3" - nM.Set(path, data, false, true) - path = []string{"Field4"} - data = "Val4" - nM.Set(path, data, false, true) - eNavMap := NavigableMap{ - data: map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": "Value2", - }, - "Field3": "Value3", - }, - "Field4": "Val4", - }, - } - if !reflect.DeepEqual(nM.data, eNavMap.data) { - t.Errorf("Expecting: %+v, received: %+v", eNavMap.data, nM.data) - } - eOrder := [][]string{ - []string{"FistLever2", "SecondLevel2", "Field2"}, - []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - []string{"FistLever2", "Field3"}, - []string{"Field4"}, - } - if !reflect.DeepEqual(eOrder, nM.order) { - t.Errorf("Expecting: %+v, received: %+v", eOrder, nM.order) - } - -} - -type testStruct struct { - Item1 string - Item2 int -} - -func TestNavMapAdd2(t *testing.T) { - nM := NewNavigableMap(nil) - path := []string{"FistLever2", "SecondLevel2", "Field2"} - data := 123 - nM.Set(path, data, false, true) - path = []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"} - data1 := 123.123 - nM.Set(path, data1, false, true) - path = []string{"FistLever2", "Field3"} - data2 := "Value3" - nM.Set(path, data2, false, true) - path = []string{"Field4"} - data3 := &testStruct{ - Item1: "Ten", - Item2: 10, - } - nM.Set(path, data3, false, true) - eNavMap := NavigableMap{ - data: map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": 123.123, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": 123, - }, - "Field3": "Value3", - }, - "Field4": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - }, - } - if !reflect.DeepEqual(nM.data, eNavMap.data) { - t.Errorf("Expecting: %+v, received: %+v", eNavMap.data, nM.data) - } -} - -func TestNavMapSetWithAppend(t *testing.T) { - nM := NewNavigableMap(nil) - itm := &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val1", - } - nM.Set(itm.Path, []*NMItem{itm}, true, true) - itm = &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val2", - } - nM.Set(itm.Path, []*NMItem{itm}, true, 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", - }, - { - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val2", - }, - }, - }, - }, - }, - }, - order: [][]string{ - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - }, - } - if !reflect.DeepEqual(nM, eNavMap) { - t.Errorf("expecting: %+v, received: %+v", eNavMap, nM) - } - nM = NewNavigableMap(nil) - itm = &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val1", - } - nM.Set(itm.Path, []*NMItem{itm}, false, true) - itm = &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val2", - } - nM.Set(itm.Path, []*NMItem{itm}, false, 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: "Val2", - }, - }, - }, - }, - }, - }, - order: [][]string{ - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - }, - } - if !reflect.DeepEqual(nM, eNavMap) { - t.Errorf("expecting: %+v, received: %+v", eNavMap, nM) - } -} - -func TestNavMapItems(t *testing.T) { - myData := map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": "Value2", - }, - "Field3": "Value3", - }, - "Field4": "Val4", - } - nM := NewNavigableMap(myData) - 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", - }, - } - if vals := nM.Values(); !reflect.DeepEqual(len(vals), len(eItems)) { - t.Errorf("Expecting: %+v, received: %+v", - utils.ToJSON(eItems), utils.ToJSON(vals)) - } -} - -func TestNavMapItems2(t *testing.T) { - myData := map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": 123.123, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": 123, - }, - "Field3": "Value3", - }, - "Field4": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - } - nM := NewNavigableMap(myData) - eItems := []*NMItem{ - &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: 123.123, - }, - &NMItem{ - Path: []string{"FistLever2", "SecondLevel2", "Field2"}, - Data: 123, - }, - &NMItem{ - Path: []string{"FistLever2", "Field3"}, - Data: "Value3", - }, - &NMItem{ - Path: []string{"Field4"}, - Data: &testStruct{ - Item1: "Ten", - Item2: 10, - }, - }, - } - if vals := nM.Values(); !reflect.DeepEqual(len(vals), len(eItems)) { - t.Errorf("Expecting: %+v, received: %+v", - utils.ToJSON(eItems), utils.ToJSON(vals)) - } -} - -func TestNavMapOrder(t *testing.T) { - myData := 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", - }, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": &NMItem{ - Path: []string{"FistLever2", "SecondLevel2", "Field2"}, - Data: "Value2", - }, - }, - "Field3": &NMItem{ - Path: []string{"FistLever2", "Field3"}, - Data: "Value3", - }, - }, - "Field4": &NMItem{ - Path: []string{"Field4"}, - Data: "Val4", - }, - } - order := [][]string{ - []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - []string{"FistLever2", "SecondLevel2", "Field2"}, - []string{"FistLever2", "Field3"}, - []string{"Field4"}, - } - nM := NewNavigableMap(myData) - nM.order = order - eItems := []interface{}{ - &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", - }, - } - if vals := nM.Values(); !reflect.DeepEqual(vals, eItems) { - t.Errorf("Expecting: %+v, received: %+v", - utils.ToJSON(eItems), utils.ToJSON(vals)) - } -} - -func TestNavMapOrder2(t *testing.T) { - myData := 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", - }, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": &NMItem{ - Path: []string{"FistLever2", "SecondLevel2", "Field2"}, - Data: "Value2", - }, - }, - "Field3": &NMItem{ - Path: []string{"FistLever2", "Field3"}, - Data: "Value3", - }, - }, - "Field4": &NMItem{ - Path: []string{"Field4"}, - Data: "Val4", - }, - } - order := [][]string{ - []string{"FistLever2", "SecondLevel2", "Field2"}, - []string{"Field4"}, - []string{"FistLever2", "Field3"}, - []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - } - nM := NewNavigableMap(myData) - nM.order = order - eItems := []interface{}{ - &NMItem{ - Path: []string{"FistLever2", "SecondLevel2", "Field2"}, - Data: "Value2", - }, - &NMItem{ - Path: []string{"Field4"}, - Data: "Val4", - }, - &NMItem{ - Path: []string{"FistLever2", "Field3"}, - Data: "Value3", - }, - &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val1", - }, - } - 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 []interface{} - ifaceMap := map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": "Value2", - }, - "Field3": "Value3", - }, - "Field4": "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)) - } -} - -func TestNavMapString(t *testing.T) { - myData := map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": "Value2", - }, - "Field3": "Value3", - }, - "Field4": "Val4", - } - nM := NewNavigableMap(myData) - eStr := utils.ToJSON(myData) - if !reflect.DeepEqual(nM.String(), eStr) { - t.Errorf("Expecting: %+v, received: %+v", eStr, nM.String()) - } -} - -func TestNavMapAsXMLElements(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"}}, - }, - }, - }, - "FirstLevel2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "SecondLevel2", "Field2"}, - Data: "attrVal1", - Config: &FCTemplate{Tag: "AttributeTest", AttributeID: "attribute1"}}, - &NMItem{Path: []string{"FirstLevel2", "SecondLevel2", "Field2"}, - Data: "Value2"}}, - }, - "Field3": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field3"}, - Data: "Value3"}}, - "Field5": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field5"}, - Data: "Value5"}, - &NMItem{Path: []string{"FirstLevel2", "Field5"}, - Data: "attrVal5", - Config: &FCTemplate{Tag: "AttributeTest", AttributeID: "attribute5"}}}, - "Field6": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field6"}, - Data: "Value6", - Config: &FCTemplate{Tag: "NewBranchTest", NewBranch: true}}, - &NMItem{Path: []string{"FirstLevel2", "Field6"}, - Data: "attrVal6", - Config: &FCTemplate{Tag: "AttributeTest", AttributeID: "attribute6"}}, - }, - }, - "Field4": []*NMItem{ - &NMItem{Path: []string{"Field4"}, - Data: "Val4"}, - &NMItem{Path: []string{"Field4"}, - Data: "attrVal2", - Config: &FCTemplate{Tag: "AttributeTest", AttributeID: "attribute2"}}}, - }, - order: [][]string{ - []string{"FirstLevel2", "SecondLevel2", "Field2"}, - []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - []string{"FirstLevel2", "Field3"}, - []string{"FirstLevel2", "Field5"}, - []string{"Field4"}, - []string{"FirstLevel2", "Field6"}, - }, - } - eXMLElmnts := []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[0][0]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[0][1]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[0][2]}, - Attributes: []*xml.Attr{ - &xml.Attr{ - Name: xml.Name{Local: "attribute1"}, - Value: "attrVal1", - }, - }, - Value: "Value2", - }, - }, - }, - &XMLElement{ - XMLName: xml.Name{Local: nM.order[2][1]}, - Value: "Value3", - }, - &XMLElement{ - XMLName: xml.Name{Local: nM.order[3][1]}, - Attributes: []*xml.Attr{ - &xml.Attr{ - Name: xml.Name{Local: "attribute5"}, - Value: "attrVal5", - }, - }, - Value: "Value5", - }, - }, - }, - &XMLElement{ - XMLName: xml.Name{Local: nM.order[1][0]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[1][1]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[1][2]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[1][3]}, - Value: "Val1", - }, - }, - }, - }, - }, - }, - }, - &XMLElement{ - XMLName: xml.Name{Local: nM.order[4][0]}, - Attributes: []*xml.Attr{ - &xml.Attr{ - Name: xml.Name{Local: "attribute2"}, - Value: "attrVal2", - }, - }, - Value: "Val4", - }, - &XMLElement{ - XMLName: xml.Name{Local: nM.order[5][0]}, - Elements: []*XMLElement{ - &XMLElement{ - XMLName: xml.Name{Local: nM.order[5][1]}, - Attributes: []*xml.Attr{ - &xml.Attr{ - Name: xml.Name{Local: "attribute6"}, - Value: "attrVal6", - }, - }, - Value: "Value6", - }, - }, - }, - } - xmlEnts, err := nM.AsXMLElements() - if err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eXMLElmnts, xmlEnts) { - t.Errorf("expecting: %s, received: %s", utils.ToJSON(eXMLElmnts), utils.ToJSON(xmlEnts)) - } - eXML := []byte(` - - Value2 - - Value3 - Value5 - - - - - Val1 - - - -Val4 - - Value6 -`) - if output, err := xml.MarshalIndent(xmlEnts, "", " "); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eXML, output) { - t.Errorf("expecting: \n%s, received: \n%s\n", string(eXML), string(output)) - } -} - -func TestIndexMapPaths(t *testing.T) { - mp := make(map[string]interface{}) - parsedPaths := make([][]string, 0) - eParsedPaths := make([][]string, 0) - if indexMapPaths(mp, nil, &parsedPaths); !reflect.DeepEqual(eParsedPaths, parsedPaths) { - t.Errorf("expecting: %+v, received: %+v", eParsedPaths, parsedPaths) - } - mp = map[string]interface{}{ - "a": "a", - } - parsedPaths = make([][]string, 0) - eParsedPaths = [][]string{ - []string{"a"}, - } - if indexMapPaths(mp, nil, &parsedPaths); !reflect.DeepEqual(eParsedPaths, parsedPaths) { - t.Errorf("expecting: %+v, received: %+v", eParsedPaths, parsedPaths) - } - mp = map[string]interface{}{ - "a": map[string]interface{}{ - "a1": "a", - }, - "b": "b", - } - parsedPaths = make([][]string, 0) - eParsedPaths = [][]string{ - []string{"a", "a1"}, - []string{"b"}, - } - if indexMapPaths(mp, nil, &parsedPaths); len(eParsedPaths) != len(parsedPaths) { - t.Errorf("expecting: %+v, received: %+v", eParsedPaths, parsedPaths) - } -} - -func TestNavMapAsCGREvent(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"}}, - }, - }, - }, - "FirstLevel2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "SecondLevel2", "Field2"}, - Data: "attrVal1", - Config: &FCTemplate{Tag: "AttributeTest", - AttributeID: "attribute1"}}, - &NMItem{Path: []string{"FirstLevel2", "SecondLevel2", "Field2"}, - Data: "Value2"}}, - }, - "Field3": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field3"}, - Data: "Value3"}}, - "Field5": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field5"}, - Data: "Value5"}, - &NMItem{Path: []string{"FirstLevel2", "Field5"}, - Data: "attrVal5", - Config: &FCTemplate{Tag: "AttributeTest", - AttributeID: "attribute5"}}}, - "Field6": []*NMItem{ - &NMItem{Path: []string{"FirstLevel2", "Field6"}, - Data: "Value6", - Config: &FCTemplate{Tag: "NewBranchTest", - NewBranch: true}}, - &NMItem{Path: []string{"FirstLevel2", "Field6"}, - Data: "attrVal6", - Config: &FCTemplate{Tag: "AttributeTest", - AttributeID: "attribute6"}}, - }, - }, - "Field4": []*NMItem{ - &NMItem{Path: []string{"Field4"}, - Data: "Val4"}, - &NMItem{Path: []string{"Field4"}, - Data: "attrVal2", - Config: &FCTemplate{Tag: "AttributeTest", - AttributeID: "attribute2"}}}, - }, - } - eEv := map[string]interface{}{ - "FirstLevel2.SecondLevel2.Field2": "Value2", - "FirstLevel.SecondLevel.ThirdLevel.Fld1": "Val1", - "FirstLevel2.Field3": "Value3", - "FirstLevel2.Field5": "Value5", - "FirstLevel2.Field6": "Value6", - "Field4": "Val4", - } - if cgrEv := nM.AsCGREvent("cgrates.org", - utils.NestingSep); cgrEv.Tenant != "cgrates.org" || - cgrEv.Time == nil || - !reflect.DeepEqual(eEv, cgrEv.Event) { - t.Errorf("expecting: %+v, \nreceived: %+v", utils.ToJSON(eEv), utils.ToJSON(cgrEv.Event)) - } -} - -func TestNavMapMerge(t *testing.T) { - nM2 := &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", - }, - { - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val2", - }, - }, - }, - }, - }, - }, - order: [][]string{ - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - {"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - }, - } - nM := NewNavigableMap(nil) - nM.Merge(nM2) - if !reflect.DeepEqual(nM2, nM) { - t.Errorf("expecting: %+v, received: %+v", nM2, nM) - } -} - -func TestNavMapGetIndex(t *testing.T) { - nM := NewNavigableMap(nil) - var expIndx *int - var expPath string - var path string = "Fld1" - expPath = path - if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx { - t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx) - } - - path = "slice[5]" - expPath = "slice" - expIndx = utils.IntPointer(5) - if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx { - t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx) - } - - path = "slice[~cgreq.Count]" - expPath = "slice" - expIndx = nil - if rplyPath, rplyIndx := nM.getIndex(path); rplyPath != expPath && rplyIndx != expIndx { - t.Errorf("Expected: path=%s ,indx=%v, received: path=%s ,indx=%v", expPath, expIndx, rplyPath, rplyIndx) - } -} - -func TestNavMapGetNextMap(t *testing.T) { - nM := NewNavigableMap(nil) - mp := map[string]interface{}{ - "field1": 10, - "field2": []string{"val1", "val2"}, - "field3": []int{1, 2, 3}, - "map1": map[string]interface{}{"field1": 100000}, - "map2": []map[string]interface{}{map[string]interface{}{"field2": 11}}, - "map3": []NavigableMap{NavigableMap{data: map[string]interface{}{"field4": 112}}}, - } - path := "map4" - if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() { - t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err) - } - path = "map2[10]" - if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() { - t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err) - } - path = "field1" - experr := fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", - mp[path], mp[path], path) - if _, err := nM.getNextMap(mp, path); err != nil && err.Error() != experr.Error() { - t.Errorf("Expected error: %s , received error %v", experr.Error(), err) - } - path = "map1" - expMap := map[string]interface{}{"field1": 100000} - if rm, err := nM.getNextMap(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(rm, expMap) { - t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm)) - } - path = "map2[0]" - expMap = map[string]interface{}{"field2": 11} - if rm, err := nM.getNextMap(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(rm, expMap) { - t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm)) - } - path = "map3[0]" - expMap = map[string]interface{}{"field4": 112} - if rm, err := nM.getNextMap(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(rm, expMap) { - t.Errorf("Expected: %s, received: %s", utils.ToJSON(expMap), utils.ToJSON(rm)) - } -} - -func TestNavMapgetLastItem(t *testing.T) { - nM := NewNavigableMap(nil) - mp := map[string]interface{}{ - "field1": 10, - "field2": []string{"val1", "val2"}, - "field3": []int{1, 2, 3}, - "map1": map[string]interface{}{"field1": 100000}, - "map2": []map[string]interface{}{map[string]interface{}{"field2": 11}}, - "map3": []NavigableMap{NavigableMap{data: map[string]interface{}{"field4": 112}}}, - } - path := "map4" - if _, err := nM.getLastItem(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() { - t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err) - } - path = "map2[10]" - if _, err := nM.getLastItem(mp, path); err != nil && err.Error() != utils.ErrNotFound.Error() { - t.Errorf("Expected error: %s , received error %v", utils.ErrNotFound.Error(), err) - } - path = "field1" - var expVal interface{} = 10 - if rplyVal, err := nM.getLastItem(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(expVal, rplyVal) { - t.Errorf("Expected: %v ,received: %v", expVal, rplyVal) - } - - path = "field2[1]" - expVal = "val2" - if rplyVal, err := nM.getLastItem(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(expVal, rplyVal) { - t.Errorf("Expected: %v ,received: %v", expVal, rplyVal) - } - path = "field3[2]" - expVal = 3 - if rplyVal, err := nM.getLastItem(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(expVal, rplyVal) { - t.Errorf("Expected: %v ,received: %v", expVal, rplyVal) - } - path = "field2" - expVal = []string{"val1", "val2"} - if rplyVal, err := nM.getLastItem(mp, path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(expVal, rplyVal) { - t.Errorf("Expected: %v ,received: %v", expVal, rplyVal) - } -} - -func TestNavMapGetField(t *testing.T) { - nM := &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", - }, - { - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val2", - }, - }, - }, - }, - }, - "FirstLevel2": map[string]interface{}{ - "SecondLevel2": []map[string]interface{}{ - map[string]interface{}{ - "ThirdLevel2": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - map[string]interface{}{ - "Count": 10, - "ThirdLevel2": map[string]interface{}{ - "Fld2": []string{"Val1", "Val2", "Val3"}, - }, - }, - }, - }, - "AnotherFirstLevel": "ValAnotherFirstLevel", - }, - } - pth := []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1[0]"} - eFld := &NMItem{ - Path: []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"}, - Data: "Val1", - } - if fld, err := nM.GetField(pth); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eFld, fld) { - t.Errorf("expecting: %s, received: %s", utils.ToIJSON(eFld), utils.ToIJSON(fld)) - } - eFld2 := map[string]interface{}{"Fld1": "Val1"} - pth = []string{"FirstLevel2", "SecondLevel2[0]", "ThirdLevel2"} - if fld, err := nM.GetField(pth); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eFld2, fld) { - t.Errorf("expecting: %s, received: %s", utils.ToIJSON(eFld2), utils.ToIJSON(fld)) - } - eFld3 := "ValAnotherFirstLevel" - pth = []string{"AnotherFirstLevel"} - if fld, err := nM.GetField(pth); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eFld3, fld) { - t.Errorf("expecting: %s, received: %s", utils.ToIJSON(eFld3), utils.ToIJSON(fld)) - } - pth = []string{"AnotherFirstLevel2"} - if _, err := nM.GetField(pth); err == nil || err != utils.ErrNotFound { - t.Error(err) - } - pth = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel", "Fld1[0]"} - if _, err := nM.GetField(pth); err == nil || err != utils.ErrNotFound { - t.Error(err) - } -} - -func TestNavMapFieldAsInterface(t *testing.T) { - nM := &NavigableMap{ - data: map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": []map[string]interface{}{ - map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": "Val1", - }, - }, - map[string]interface{}{ - "Count": 10, - "ThirdLevel2": map[string]interface{}{ - "Fld2": []string{"Val1", "Val2", "Val3"}, - }, - }, - }, - }, - "AnotherFirstLevel": "ValAnotherFirstLevel", - }, - } - - path := []string{"FirstLevel", "SecondLevel[0]", "Count"} - expErr := utils.ErrNotFound - var eVal interface{} = nil - if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() { - t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err) - } - - path = []string{"AnotherFirstLevel", "SecondLevel", "Count"} - expErr = fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", - nM.data["AnotherFirstLevel"], nM.data["AnotherFirstLevel"], "AnotherFirstLevel") - if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() { - t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err) - } - - path = []string{"FirstLevel", "SecondLevel[1]", "Count"} - eVal = 10 - if rplyVal, err := nM.FieldAsInterface(path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eVal, rplyVal) { - t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal)) - } - - path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2"} - eVal = []string{"Val1", "Val2", "Val3"} - if rplyVal, err := nM.FieldAsInterface(path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eVal, rplyVal) { - t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal)) - } - - path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2[2]"} - eVal = "Val3" - if rplyVal, err := nM.FieldAsInterface(path); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eVal, rplyVal) { - t.Errorf("Expected: %s , received: %s", utils.ToJSON(eVal), utils.ToJSON(rplyVal)) - } -} - -func TestNavMapGetKeys(t *testing.T) { - navMp := NewNavigableMap( - map[string]interface{}{ - "FirstLevel": map[string]interface{}{ - "SecondLevel": map[string]interface{}{ - "ThirdLevel": map[string]interface{}{ - "Fld1": 123.123, - }, - }, - }, - "FistLever2": map[string]interface{}{ - "SecondLevel2": map[string]interface{}{ - "Field2": 123, - }, - "Field3": "Value3", - "Field4": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - }, - "Field5": &testStruct{ - Item1: "Ten", - Item2: 10, - }, - "Field6": []string{"1", "2"}, - }, - ) - expKeys := []string{ - "FirstLevel", - "FirstLevel.SecondLevel", - "FirstLevel.SecondLevel.ThirdLevel", - "FirstLevel.SecondLevel.ThirdLevel.Fld1", - "FistLever2", - "FistLever2.SecondLevel2", - "FistLever2.SecondLevel2.Field2", - "FistLever2.Field3", - "FistLever2.Field4", - "FistLever2.Field4.Item1", - "FistLever2.Field4.Item2", - "Field5", - "Field5.Item1", - "Field5.Item2", - "Field6", - "Field6[0]", - "Field6[1]", - } - keys := navMp.GetKeys() - sort.Strings(expKeys) - sort.Strings(keys) - if !reflect.DeepEqual(expKeys, keys) { - t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expKeys), utils.ToJSON(keys)) - } -} - func TestNMAsXMLElements(t *testing.T) { nM := utils.NewOrderedNavigableMap() order := []utils.PathItems{ @@ -1569,12 +412,12 @@ func BenchmarkOrderdNavigableMapSet2(b *testing.B) { } } -func BenchmarkNavigableMapOld1Set(b *testing.B) { - nm := NewNavigableMap(nil) - b.ResetTimer() - for n := 0; n < b.N; n++ { - for _, data := range gen { - nm.Set(data.path, data.data, false, true) - } - } -} +// func BenchmarkNavigableMapOld1Set(b *testing.B) { +// nm := NewNavigableMap(nil) +// b.ResetTimer() +// for n := 0; n < b.N; n++ { +// for _, data := range gen { +// nm.Set(data.path, data.data, false, true) +// } +// } +// } diff --git a/config/slicedp.go b/config/slicedp.go index 7dac8430e..ee7a69567 100644 --- a/config/slicedp.go +++ b/config/slicedp.go @@ -28,14 +28,14 @@ import ( // NewSliceDP constructs a utils.DataProvider func NewSliceDP(record []string) (dP utils.DataProvider) { - dP = &SliceDP{req: record, cache: NewNavigableMap(nil)} + dP = &SliceDP{req: record, cache: utils.MapStorage{}} return } // SliceDP implements engine.utils.DataProvider so we can pass it to filters type SliceDP struct { req []string - cache *NavigableMap + cache utils.MapStorage } // String is part of engine.utils.DataProvider interface @@ -65,7 +65,7 @@ func (cP *SliceDP) FieldAsInterface(fldPath []string) (data interface{}, err err } else { data = cP.req[cfgFieldIdx] } - cP.cache.Set(fldPath, data, false, false) + cP.cache.Set(fldPath, data) return } diff --git a/config/xmldp.go b/config/xmldp.go index 13bf8549a..2c48a7a6c 100644 --- a/config/xmldp.go +++ b/config/xmldp.go @@ -30,7 +30,7 @@ import ( // NewXmlProvider constructs a utils.DataProvider func NewXmlProvider(req *xmlquery.Node, cdrPath utils.HierarchyPath) (dP utils.DataProvider) { - dP = &XmlProvider{req: req, cdrPath: cdrPath, cache: NewNavigableMap(nil)} + dP = &XmlProvider{req: req, cdrPath: cdrPath, cache: utils.MapStorage{}} return } @@ -38,7 +38,7 @@ func NewXmlProvider(req *xmlquery.Node, cdrPath utils.HierarchyPath) (dP utils.D type XmlProvider struct { req *xmlquery.Node cdrPath utils.HierarchyPath //used to compute relative path - cache *NavigableMap + cache utils.MapStorage } // String is part of engine.utils.DataProvider interface @@ -77,7 +77,7 @@ func (xP *XmlProvider) FieldAsInterface(fldPath []string) (data interface{}, err } } data, err = ElementText(xP.req, relPath.AsString("/", false)) - xP.cache.Set(fldPath, data, false, false) + xP.cache.Set(fldPath, data) return } diff --git a/dispatchers/dispatchers.go b/dispatchers/dispatchers.go index dc7d075c2..4ab28a931 100755 --- a/dispatchers/dispatchers.go +++ b/dispatchers/dispatchers.go @@ -138,8 +138,7 @@ func (dS *DispatcherService) dispatcherProfileForEvent(ev *utils.CGREvent, return nil, err } } - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, ev.Event, false, false) + evNm := utils.MapStorage{utils.MetaReq: ev.Event} for prflID := range prflIDs { prfl, err := dS.dm.GetDispatcherProfile(ev.Tenant, prflID, true, true, utils.NonTransactional) if err != nil { diff --git a/ees/ees.go b/ees/ees.go index 1ebcde7db..d8e31b868 100644 --- a/ees/ees.go +++ b/ees/ees.go @@ -140,9 +140,7 @@ func (eeS *EventExporterS) V1ProcessEvent(cgrEv *utils.CGREventWithOpts, rply *s } if len(eeCfg.Filters) != 0 { - cgrDp := config.NewNavigableMap(map[string]interface{}{ - utils.MetaReq: cgrEv.Event, - }) + cgrDp := utils.MapStorage{utils.MetaReq: cgrEv.Event} tnt := cgrEv.Tenant if eeTnt, errTnt := eeCfg.Tenant.ParseEvent(cgrEv.Event); errTnt == nil && eeTnt != utils.EmptyString { tnt = eeTnt diff --git a/ees/filecsv.go b/ees/filecsv.go index 39d9495f4..a550ce0b8 100644 --- a/ees/filecsv.go +++ b/ees/filecsv.go @@ -103,9 +103,7 @@ func (fCsv *FileCSVee) ExportEvent(cgrEv *utils.CGREvent) (err error) { defer fCsv.Unlock() fCsv.numberOfEvents++ var csvRecord []string - navMp := config.NewNavigableMap(map[string]interface{}{ - utils.MetaReq: cgrEv.Event, - }) + navMp := utils.MapStorage{utils.MetaReq: cgrEv.Event} for _, cfgFld := range fCsv.cgrCfg.EEsCfg().Exporters[fCsv.cfgIdx].ContentFields() { if pass, err := fCsv.filterS.Pass(cgrEv.Tenant, cfgFld.Filters, navMp); err != nil || !pass { diff --git a/engine/action.go b/engine/action.go index bf7b86b2a..3b8cc95e2 100644 --- a/engine/action.go +++ b/engine/action.go @@ -894,7 +894,7 @@ func (apl Actions) Clone() (interface{}, error) { // newCdrLogProvider constructs a DataProvider func newCdrLogProvider(acnt *Account, action *Action) (dP utils.DataProvider) { - dP = &cdrLogProvider{acnt: acnt, action: action, cache: config.NewNavigableMap(nil)} + dP = &cdrLogProvider{acnt: acnt, action: action, cache: utils.MapStorage{}} return } @@ -902,7 +902,7 @@ func newCdrLogProvider(acnt *Account, action *Action) (dP utils.DataProvider) { type cdrLogProvider struct { acnt *Account action *Action - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -963,7 +963,7 @@ func (cdrP *cdrLogProvider) FieldAsInterface(fldPath []string) (data interface{} default: data = fldPath[0] } - cdrP.cache.Set(fldPath, data, false, false) + cdrP.cache.Set(fldPath, data) return } diff --git a/engine/attributes.go b/engine/attributes.go index 69d3e6db2..23e425853 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -95,10 +95,10 @@ func (alS *AttributeService) attributeProfileForEvent(args *AttrArgsProcessEvent attrIDs = aPrflIDs.Slice() } - evNm := config.NewNavigableMap(map[string]interface{}{ + evNm := utils.MapStorage{ utils.MetaReq: args.Event, utils.MetaOpts: args.Opts, - }) + } for _, apID := range attrIDs { aPrfl, err := alS.dm.GetAttributeProfile(args.Tenant, apID, true, true, utils.NonTransactional) if err != nil { @@ -177,10 +177,10 @@ func (alS *AttributeService) processEvent(args *AttrArgsProcessEvent) ( Opts: args.Opts, blocker: attrPrf.Blocker, } - evNm := config.NewNavigableMap(map[string]interface{}{ + evNm := utils.MapStorage{ utils.MetaReq: rply.CGREvent.Event, utils.MetaOpts: rply.Opts, - }) + } for _, attribute := range attrPrf.Attributes { //in case that we have filter for attribute send them to FilterS to be processed if len(attribute.FilterIDs) != 0 { @@ -337,7 +337,7 @@ func (alS *AttributeService) processEvent(args *AttrArgsProcessEvent) ( val, err = evNm.FieldAsString(strings.Split(attribute.Path, utils.NestingSep)) substitute = val + substitute } - evNm.Set(strings.Split(attribute.Path, utils.NestingSep), substitute, false, false) + evNm.Set(strings.Split(attribute.Path, utils.NestingSep), substitute) } return } diff --git a/engine/cdr.go b/engine/cdr.go index 7505a583b..e232ade68 100644 --- a/engine/cdr.go +++ b/engine/cdr.go @@ -154,10 +154,10 @@ func (cdr *CDR) FormatCost(shiftDecimals, roundDecimals int) string { // FieldAsString is used to retrieve fields as string, primary fields are const labeled func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err error) { parsed, err = rsrPrs.ParseDataProviderWithInterfaces( - config.NewNavigableMap(map[string]interface{}{ + utils.MapStorage{ utils.MetaReq: cdr.AsMapStringIface(), utils.MetaEC: cdr.CostDetails, - }), utils.NestingSep) + }, utils.NestingSep) if err != nil { return } @@ -167,10 +167,10 @@ func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err erro // FieldsAsString concatenates values of multiple fields defined in template, used eg in CDR templates func (cdr *CDR) FieldsAsString(rsrFlds config.RSRParsers) string { outVal, err := rsrFlds.ParseDataProviderWithInterfaces( - config.NewNavigableMap(map[string]interface{}{ + utils.MapStorage{ utils.MetaReq: cdr.AsMapStringIface(), utils.MetaEC: cdr.CostDetails, - }), utils.NestingSep) + }, utils.NestingSep) if err != nil { return "" } @@ -403,10 +403,10 @@ func (cdr *CDR) formatField(cfgFld *config.FCTemplate, httpSkipTLSCheck bool, // ExportRecord is a []string to keep it compatible with encoding/csv Writer func (cdr *CDR) AsExportRecord(exportFields []*config.FCTemplate, httpSkipTLSCheck bool, groupedCDRs []*CDR, filterS *FilterS) (expRecord []string, err error) { - nM := config.NewNavigableMap(map[string]interface{}{ + nM := utils.MapStorage{ utils.MetaReq: cdr.AsMapStringIface(), utils.MetaEC: cdr.CostDetails, - }) + } for _, cfgFld := range exportFields { if !strings.HasPrefix(cfgFld.Path, utils.MetaExp+utils.NestingSep) { continue @@ -433,10 +433,10 @@ func (cdr *CDR) AsExportRecord(exportFields []*config.FCTemplate, func (cdr *CDR) AsExportMap(exportFields []*config.FCTemplate, httpSkipTLSCheck bool, groupedCDRs []*CDR, filterS *FilterS) (expMap map[string]string, err error) { expMap = make(map[string]string) - nM := config.NewNavigableMap(map[string]interface{}{ + nM := utils.MapStorage{ utils.MetaReq: cdr.AsMapStringIface(), utils.MetaEC: cdr.CostDetails, - }) + } for _, cfgFld := range exportFields { if !strings.HasPrefix(cfgFld.Path, utils.MetaExp+utils.NestingSep) { continue diff --git a/engine/cdre.go b/engine/cdre.go index 66b42da4c..51e0159df 100644 --- a/engine/cdre.go +++ b/engine/cdre.go @@ -390,10 +390,10 @@ func (cdre *CDRExporter) processCDRs() (err error) { continue } if len(cdre.exportTemplate.Filters) != 0 { - cgrDp := config.NewNavigableMap(map[string]interface{}{ + cgrDp := utils.MapStorage{ utils.MetaReq: cdr.AsMapStringIface(), utils.MetaEC: cdr.CostDetails, - }) + } if pass, err := cdre.filterS.Pass(cdre.exportTemplate.Tenant, cdre.exportTemplate.Filters, cgrDp); err != nil || !pass { continue // Not passes filters, ignore this CDR diff --git a/engine/chargers.go b/engine/chargers.go index 81d3e0c2a..0bc2af3be 100644 --- a/engine/chargers.go +++ b/engine/chargers.go @@ -68,8 +68,7 @@ func (cS *ChargerService) matchingChargerProfilesForEvent(cgrEv *utils.CGREvent) return nil, err } matchingCPs := make(map[string]*ChargerProfile) - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, cgrEv.Event, false, false) + evNm := utils.MapStorage{utils.MetaReq: cgrEv.Event} for cpID := range cpIDs { cP, err := cS.dm.GetChargerProfile(cgrEv.Tenant, cpID, true, true, utils.NonTransactional) if err != nil { diff --git a/engine/eventcost.go b/engine/eventcost.go index 1a9725762..3e840b9f5 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -24,7 +24,6 @@ import ( "net" "time" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -37,7 +36,7 @@ func NewBareEventCost() *EventCost { Rates: make(ChargedRates), Timings: make(ChargedTimings), Charges: make([]*ChargingInterval, 0), - cache: config.NewNavigableMap(nil), + cache: utils.MapStorage{}, } } @@ -119,12 +118,12 @@ type EventCost struct { Rates ChargedRates Timings ChargedTimings - cache *config.NavigableMap + cache utils.MapStorage } func (ec *EventCost) initCache() { if ec != nil { - ec.cache = config.NewNavigableMap(nil) + ec.cache = utils.MapStorage{} } } @@ -867,9 +866,9 @@ func (ec *EventCost) FieldAsInterface(fldPath []string) (val interface{}, err er } val, err = ec.fieldAsInterface(fldPath) if err == nil { - ec.cache.Set(fldPath, val, false, false) + ec.cache.Set(fldPath, val) } else if err == utils.ErrNotFound { - ec.cache.Set(fldPath, nil, false, false) + ec.cache.Set(fldPath, nil) } return } diff --git a/engine/eventcost_test.go b/engine/eventcost_test.go index c99563d13..f9f339e4c 100644 --- a/engine/eventcost_test.go +++ b/engine/eventcost_test.go @@ -204,7 +204,7 @@ var testEC = &EventCost{ StartTime: "00:00:00", }, }, - cache: config.NewNavigableMap(nil), + cache: utils.MapStorage{}, } func TestECClone(t *testing.T) { @@ -2515,7 +2515,7 @@ func TestECSyncKeys(t *testing.T) { StartTime: "00:00:00", }, }, - cache: config.NewNavigableMap(nil), + cache: utils.MapStorage{}, } ec.SyncKeys(refEC) @@ -2552,7 +2552,7 @@ func TestECAsDataProvider(t *testing.T) { func TestInitCache(t *testing.T) { eventCost := &EventCost{} eventCost.initCache() - eOut := config.NewNavigableMap(nil) + eOut := utils.MapStorage{} if !reflect.DeepEqual(eOut, eventCost.cache) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eOut), utils.ToJSON(eventCost.cache)) } @@ -2568,14 +2568,14 @@ func TestEventCostFieldAsInterface(t *testing.T) { t.Errorf("Expecting: nil, received: %+v", rcv) } // item found in cache - eventCost.cache = config.NewNavigableMap(map[string]interface{}{"test": nil}) + eventCost.cache = utils.MapStorage{"test": nil} if rcv, err := eventCost.FieldAsInterface([]string{"test"}); err == nil || err != utils.ErrNotFound { t.Errorf("Expecting: nil, received: %+v", err) } else if rcv != nil { t.Errorf("Expecting: nil, received: %+v", rcv) } // data found in cache - eventCost.cache = config.NewNavigableMap(map[string]interface{}{"test": "test"}) + eventCost.cache = utils.MapStorage{"test": "test"} if rcv, err := eventCost.FieldAsInterface([]string{"test"}); err != nil { t.Errorf("Expecting: nil, received: %+v", err) } else if rcv != "test" { diff --git a/engine/filterhelpers.go b/engine/filterhelpers.go index b355a8191..5efb8d1a4 100644 --- a/engine/filterhelpers.go +++ b/engine/filterhelpers.go @@ -33,18 +33,9 @@ func MatchingItemIDsForEvent(ev map[string]interface{}, stringFldIDs, prefixFldI dm *DataManager, cacheID, itemIDPrefix string, indexedSelects, nestedFields bool) (itemIDs utils.StringMap, err error) { itemIDs = make(utils.StringMap) var allFieldIDs []string - navEv := config.NewNavigableMap(ev) + navEv := utils.MapStorage(ev) if indexedSelects && (stringFldIDs == nil || prefixFldIDs == nil) { - if !nestedFields { - allFieldIDs = make([]string, len(ev)) - i := 0 - for fldID := range ev { - allFieldIDs[i] = fldID - i++ - } - } else { - allFieldIDs = navEv.GetKeys() - } + allFieldIDs = navEv.GetKeys(nestedFields) } // Guard will protect the function with automatic locking lockID := utils.CacheInstanceToPrefix[cacheID] + itemIDPrefix diff --git a/engine/filters.go b/engine/filters.go index 5e5c56ae5..f4920f9ff 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -521,7 +521,7 @@ func newDynamicDP(cfg *config.CGRConfig, connMgr *ConnManager, connMgr: connMgr, tenant: tenant, initialDP: initialDP, - cache: config.NewNavigableMap(nil), + cache: utils.MapStorage{}, } } @@ -531,7 +531,7 @@ type dynamicDP struct { tenant string initialDP utils.DataProvider - cache *config.NavigableMap + cache utils.MapStorage } func (dDP *dynamicDP) String() string { return utils.ToJSON(dDP) } @@ -583,7 +583,7 @@ func (dDP *dynamicDP) fieldAsInterface(fldPath []string) (val interface{}, err e } //construct dataProvider from account and set it furthder dp := config.NewObjectDP(account) - dDP.cache.Set(fldPath[:2], dp, false, false) + dDP.cache.Set(fldPath[:2], dp) return dp.FieldAsInterface(fldPath[2:]) case utils.MetaResources: // sample of fieldName : ~*resources.ResourceID.Field @@ -593,7 +593,7 @@ func (dDP *dynamicDP) fieldAsInterface(fldPath []string) (val interface{}, err e return nil, err } dp := config.NewObjectDP(reply) - dDP.cache.Set(fldPath[:2], dp, false, false) + dDP.cache.Set(fldPath[:2], dp) return dp.FieldAsInterface(fldPath[2:]) case utils.MetaStats: // sample of fieldName : ~*stats.StatID.*acd @@ -605,7 +605,7 @@ func (dDP *dynamicDP) fieldAsInterface(fldPath []string) (val interface{}, err e return nil, err } for k, v := range statValues { - dDP.cache.Set([]string{utils.MetaStats, fldPath[1], k}, v, false, false) + dDP.cache.Set([]string{utils.MetaStats, fldPath[1], k}, v) } return dDP.cache.FieldAsInterface(fldPath) default: // in case of constant we give an empty DataProvider ( empty navigable map ) diff --git a/engine/filters_test.go b/engine/filters_test.go index 51498a094..ea7f5cc71 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -310,15 +310,15 @@ func TestFilterPassGreaterThan(t *testing.T) { if err != nil { t.Error(err) } - ev := config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 20, false, true) + ev := utils.MapStorage{} + ev.Set([]string{"ASR"}, 20) if passes, err := rf.passGreaterThan(ev); err != nil { t.Error(err) } else if !passes { t.Error("not passing") } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 40, false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, 40) if passes, err := rf.passGreaterThan(ev); err != nil { t.Error(err) } else if passes { @@ -351,8 +351,8 @@ func TestFilterPassGreaterThan(t *testing.T) { } else if !passes { t.Error("not passing") } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 20, false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, 20) if passes, err := rf.passGreaterThan(ev); err != nil { t.Error(err) } else if passes { @@ -362,16 +362,16 @@ func TestFilterPassGreaterThan(t *testing.T) { if err != nil { t.Error(err) } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ACD"}, time.Duration(2*time.Minute), false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ACD"}, time.Duration(2*time.Minute)) if passes, err := rf.passGreaterThan(ev); err != nil { t.Error(err) } else if !passes { t.Error("not pass") } // Second - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, time.Duration(20*time.Second), false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, time.Duration(20*time.Second)) rf, err = NewFilterRule("*gte", "~ASR", []string{"10s"}) if err != nil { t.Error(err) @@ -391,8 +391,8 @@ func TestFilterPassGreaterThan(t *testing.T) { } else if !passes { t.Error("passing") } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, float64(20*time.Second), false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, float64(20*time.Second)) rf, err = NewFilterRule("*gte", "~ASR", []string{"10s"}) if err != nil { t.Error(err) @@ -403,8 +403,8 @@ func TestFilterPassGreaterThan(t *testing.T) { t.Error("passing") } //Here converter will be consider part of path and will get error : NOT_FOUND - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 20, false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, 20) rf, err = NewFilterRule("*gte", "~ASR{*duration_seconds}", []string{"10"}) if err != nil { t.Error(err) @@ -415,8 +415,8 @@ func TestFilterPassGreaterThan(t *testing.T) { t.Error("passing") } //Here converter will be consider part of path and will get error : NOT_FOUND - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 20, false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, 20) rf, err = NewFilterRule("*gte", "~ASR{*duration_seconds}", []string{"10s"}) if err != nil { t.Error(err) @@ -433,15 +433,15 @@ func TestFilterpassEqualTo(t *testing.T) { if err != nil { t.Error(err) } - ev := config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 40.0, false, true) + ev := utils.MapStorage{} + ev.Set([]string{"ASR"}, 40.0) if passes, err := rf.passEqualTo(ev); err != nil { t.Error(err) } else if !passes { t.Error("not passing") } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, 39, false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, 39) if passes, err := rf.passEqualTo(ev); err != nil { t.Error(err) } else if passes { @@ -456,8 +456,8 @@ func TestFilterpassEqualTo(t *testing.T) { } else if !passes { t.Error("not passing", passes) } - ev = config.NewNavigableMap(nil) - ev.Set([]string{"ASR"}, "string1", false, true) + ev = utils.MapStorage{} + ev.Set([]string{"ASR"}, "string1") rf, err = NewFilterRule(utils.MetaEqual, "~ASR", []string{"string1"}) if err != nil { t.Error(err) @@ -574,8 +574,8 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent := map[string]interface{}{ "Account": "1007", } - fEv := config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, failEvent, false, false) + fEv := utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) if pass, err := filterS.Pass("cgrates.org", []string{"*string:~*req.Account:1007:error"}, fEv); err != nil { t.Error(err) @@ -588,8 +588,8 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { } else if pass { t.Errorf("Expecting: %+v, received: %+v", false, pass) } - pEv := config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent, false, false) + pEv := utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) if pass, err := filterS.Pass("cgrates.org", []string{"*string:~*req.Account:1007"}, pEv); err != nil { t.Errorf(err.Error()) @@ -609,10 +609,10 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ "Account": "1007", } - fEv = config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, failEvent, false, false) - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent, false, false) + fEv = utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) if pass, err := filterS.Pass("cgrates.org", []string{"*prefix:~*req.Account:10"}, fEv); err != nil { t.Errorf(err.Error()) @@ -650,10 +650,10 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ "Tenant": "cgrates.org", } - fEv = config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, failEvent, false, false) - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent, false, false) + fEv = utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Tenant(~^cgr.*\\.org$)"}, fEv); err != nil { t.Errorf(err.Error()) @@ -679,10 +679,10 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent = map[string]interface{}{ utils.Weight: 20, } - fEv = config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, failEvent, false, false) - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent, false, false) + fEv = utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) if pass, err := filterS.Pass("cgrates.org", []string{"*gte:~*req.Weight:20"}, fEv); err != nil { t.Errorf(err.Error()) @@ -716,10 +716,10 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { "EmptyPtrSlice": &[]string{}, "EmptyPtrMap": &map[string]string{}, } - fEv = config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, failEvent, false, false) - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent, false, false) + fEv = utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) for key := range failEvent { if pass, err := filterS.Pass("cgrates.org", []string{"*empty:~*req." + key + ":"}, fEv); err != nil { @@ -768,10 +768,10 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { utils.Destination: "+4986517174963", utils.Weight: 20, } - pEv1 := config.NewNavigableMap(nil) - pEv1.Set([]string{utils.MetaReq}, passEvent1, false, false) - pEv2 := config.NewNavigableMap(nil) - pEv2.Set([]string{utils.MetaReq}, passEvent2, false, false) + pEv1 := utils.MapStorage{} + pEv1.Set([]string{utils.MetaReq}, passEvent1) + pEv2 := utils.MapStorage{} + pEv2.Set([]string{utils.MetaReq}, passEvent2) if pass, err := filterS.Pass("cgrates.org", []string{}, pEv1); err != nil { t.Errorf(err.Error()) @@ -787,8 +787,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { ev := map[string]interface{}{ "Test": "MultipleCharacter", } - pEv := config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, ev, false, false) + pEv := utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, ev) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Test(~^\\w{30,})"}, pEv); err != nil { t.Errorf(err.Error()) @@ -798,8 +798,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { ev = map[string]interface{}{ "Test": "MultipleCharacter123456789MoreThan30Character", } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, ev, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, ev) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Test(~^\\w{30,})"}, pEv); err != nil { t.Errorf(err.Error()) @@ -812,8 +812,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { "Test2": "MultipleCharacter", }, } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, ev, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, ev) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Test.Test2(~^\\w{30,})"}, pEv); err != nil { t.Errorf(err.Error()) @@ -825,8 +825,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { "Test2": "MultipleCharacter123456789MoreThan30Character", }, } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, ev, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, ev) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Test.Test2(~^\\w{30,})"}, pEv); err != nil { t.Errorf(err.Error()) @@ -841,8 +841,8 @@ func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { utils.SetupTime: time.Date(2017, 12, 1, 14, 25, 0, 0, time.UTC), utils.Usage: "1m20s", } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, ev, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, ev) if pass, err := filterS.Pass("cgrates.org", []string{"*string:~*req.Account:1003", "*prefix:~*req.Destination:10", "*suffix:~*req.Subject:03", "*rsr::~*req.Destination(1002)"}, @@ -865,8 +865,8 @@ func TestPassFilterMaxCost(t *testing.T) { passEvent1 := map[string]interface{}{ "MaxUsage": time.Duration(-1), } - pEv := config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent1, false, false) + pEv := utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent1) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.MaxUsage{*duration_nanoseconds}(>0)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -877,8 +877,8 @@ func TestPassFilterMaxCost(t *testing.T) { passEvent2 := map[string]interface{}{ "MaxUsage": time.Duration(0), } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent2, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent2) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.MaxUsage{*duration_nanoseconds}(>0)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -889,8 +889,8 @@ func TestPassFilterMaxCost(t *testing.T) { passEvent3 := map[string]interface{}{ "MaxUsage": time.Duration(123), } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent3, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent3) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.MaxUsage{*duration_nanoseconds}(>0)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -917,8 +917,8 @@ func TestPassFilterMissingField(t *testing.T) { passEvent1 := map[string]interface{}{ "test": "call", } - pEv := config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent1, false, false) + pEv := utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent1) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Category(^$)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -929,8 +929,8 @@ func TestPassFilterMissingField(t *testing.T) { passEvent2 := map[string]interface{}{ "Category": "", } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent2, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent2) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Category(^$)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -941,8 +941,8 @@ func TestPassFilterMissingField(t *testing.T) { passEvent3 := map[string]interface{}{ "Category": "call", } - pEv = config.NewNavigableMap(nil) - pEv.Set([]string{utils.MetaReq}, passEvent3, false, false) + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent3) if pass, err := filterS.Pass("cgrates.org", []string{"*rsr::~*req.Category(^$)"}, pEv); err != nil { t.Errorf(err.Error()) @@ -1086,7 +1086,7 @@ func TestEventCostFilter(t *testing.T) { }, } cd.initCache() - cgrDp := config.NewNavigableMap(map[string]interface{}{utils.MetaEC: cd}) + cgrDp := utils.MapStorage{utils.MetaEC: cd} if pass, err := filterS.Pass("cgrates.org", []string{"*string:~*ec.Charges[0].Increments[0].Accounting.Balance.Value:50"}, cgrDp); err != nil { @@ -1183,8 +1183,8 @@ func TestPassPartial(t *testing.T) { passEvent := map[string]interface{}{ "Account": "1007", } - fEv := config.NewNavigableMap(nil) - fEv.Set([]string{utils.MetaReq}, passEvent, false, false) + fEv := utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, passEvent) prefixes := []string{utils.DynamicDataPrefix + utils.MetaReq} if pass, ruleList, err := filterS.LazyPass("cgrates.org", []string{"*string:~*req.Account:1007"}, fEv, prefixes); err != nil { diff --git a/engine/libstats.go b/engine/libstats.go index d1c89db00..2fd17dd5f 100644 --- a/engine/libstats.go +++ b/engine/libstats.go @@ -247,8 +247,7 @@ func (sq *StatQueue) addStatEvent(ev *utils.CGREvent, filterS *FilterS) (err err ExpiryTime *time.Time }{ev.ID, expTime}) var pass bool - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, ev.Event, false, false) + evNm := utils.MapStorage{utils.MetaReq: ev.Event} for metricID, metric := range sq.SQMetrics { if pass, err = filterS.Pass(ev.Tenant, metric.GetFilterIDs(), evNm); err != nil { diff --git a/engine/resources.go b/engine/resources.go index cce73e262..c7650da8c 100644 --- a/engine/resources.go +++ b/engine/resources.go @@ -485,8 +485,7 @@ func (rS *ResourceService) matchingResourcesForEvent(ev *utils.CGREvent, return } } - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, ev.Event, false, false) + evNm := utils.MapStorage{utils.MetaReq: ev.Event} lockIDs := utils.PrefixSliceItems(rs.IDs(), utils.ResourcesPrefix) guardian.Guardian.Guard(func() (gIface interface{}, gErr error) { for resName := range rIDs { diff --git a/engine/routes.go b/engine/routes.go index dee11d50e..4ed8cc9e4 100644 --- a/engine/routes.go +++ b/engine/routes.go @@ -167,7 +167,7 @@ func (rpS *RouteService) matchingRouteProfilesForEvent(ev *utils.CGREvent, singl if singleResult { matchingRPrf = make([]*RouteProfile, 1) } - evNm := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: ev.Event}) + evNm := utils.MapStorage{utils.MetaReq: ev.Event} for lpID := range rPrfIDs { rPrf, err := rpS.dm.GetRouteProfile(ev.Tenant, lpID, true, true, utils.NonTransactional) if err != nil { @@ -461,9 +461,10 @@ func (rpS *RouteService) populateSortingData(ev *utils.CGREvent, route *Route, //filter the supplier if len(route.lazyCheckRules) != 0 { //construct the DP and pass it to filterS - nM := config.NewNavigableMap(nil) - nM.Set([]string{utils.MetaReq}, ev.Event, false, false) - nM.Set([]string{utils.MetaVars}, sortedSpl.SortingData, false, false) + nM := utils.MapStorage{ + utils.MetaReq: ev.Event, + utils.MetaVars: sortedSpl.SortingData, + } for _, rule := range route.lazyCheckRules { // verify the rules remaining from PartialPass if pass, err = rule.Pass(newDynamicDP(rpS.cgrcfg, rpS.connMgr, ev.Tenant, nM)); err != nil { @@ -495,8 +496,7 @@ func (rpS *RouteService) sortedRoutesForEvent(args *ArgsGetRoutes) (sortedRoutes extraOpts.sortingStragety = rPrfl.Sorting // populate sortingStrategy in extraOpts //construct the DP and pass it to filterS - nM := config.NewNavigableMap(nil) - nM.Set([]string{utils.MetaReq}, args.CGREvent.Event, false, false) + nM := utils.MapStorage{utils.MetaReq: args.CGREvent.Event} routeNew := make([]*Route, 0) // apply filters for event diff --git a/engine/stats.go b/engine/stats.go index b4d855f56..a57f9ade1 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -168,8 +168,7 @@ func (sS *StatService) matchingStatQueuesForEvent(args *StatsArgsProcessEvent) ( } sqIDs = mapIDs.Slice() } - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, args.Event, false, false) + evNm := utils.MapStorage{utils.MetaReq: args.Event} for _, sqID := range sqIDs { sqPrfl, err := sS.dm.GetStatQueueProfile(args.Tenant, sqID, true, true, utils.NonTransactional) if err != nil { diff --git a/engine/thresholds.go b/engine/thresholds.go index 2dd8fecab..c88e136dc 100644 --- a/engine/thresholds.go +++ b/engine/thresholds.go @@ -252,8 +252,9 @@ func (tS *ThresholdService) matchingThresholdsForEvent(args *ArgsProcessEvent) ( } tIDs = tIDsMap.Slice() } - evNm := config.NewNavigableMap(nil) - evNm.Set([]string{utils.MetaReq}, args.Event, false, false) + evNm := utils.MapStorage{ + utils.MetaReq: args.Event, + } for _, tID := range tIDs { tPrfl, err := tS.dm.GetThresholdProfile(args.Tenant, tID, true, true, utils.NonTransactional) if err != nil { diff --git a/ers/filejson.go b/ers/filejson.go index 2afacbf17..ea6b8d988 100644 --- a/ers/filejson.go +++ b/ers/filejson.go @@ -143,7 +143,7 @@ func (rdr *JSONFileER) processFile(fPath, fName string) (err error) { reqVars := utils.NavigableMap2{utils.FileName: utils.NewNMData(fName)} agReq := agents.NewAgentRequest( - config.NewNavigableMap(data), reqVars, + utils.MapStorage(data), reqVars, nil, nil, nil, rdr.Config().Tenant, rdr.cgrCfg.GeneralCfg().DefaultTenant, utils.FirstNonEmpty(rdr.Config().Timezone, diff --git a/ers/kafka.go b/ers/kafka.go index 7709f6802..1944c6b3b 100644 --- a/ers/kafka.go +++ b/ers/kafka.go @@ -159,7 +159,7 @@ func (rdr *KafkaER) processMessage(msg []byte) (err error) { } agReq := agents.NewAgentRequest( - config.NewNavigableMap(decodedMessage), nil, + utils.MapStorage(decodedMessage), nil, nil, nil, nil, rdr.Config().Tenant, rdr.cgrCfg.GeneralCfg().DefaultTenant, utils.FirstNonEmpty(rdr.Config().Timezone, diff --git a/ers/sql.go b/ers/sql.go index e47d11744..acda75429 100644 --- a/ers/sql.go +++ b/ers/sql.go @@ -186,7 +186,7 @@ func (rdr *SQLEventReader) readLoop(db *gorm.DB) { func (rdr *SQLEventReader) processMessage(msg map[string]interface{}) (err error) { agReq := agents.NewAgentRequest( - config.NewNavigableMap(msg), nil, + utils.MapStorage(msg), nil, nil, nil, nil, rdr.Config().Tenant, rdr.cgrCfg.GeneralCfg().DefaultTenant, utils.FirstNonEmpty(rdr.Config().Timezone, diff --git a/general_tests/filters_test.go b/general_tests/filters_test.go index af62a641c..febdd4793 100644 --- a/general_tests/filters_test.go +++ b/general_tests/filters_test.go @@ -105,8 +105,8 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { passEvent := map[string]interface{}{ utils.Destination: "+4986517174963", } - fEv := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: failEvent}) - pEv := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: passEvent}) + fEv := utils.MapStorage{utils.MetaReq: failEvent} + pEv := utils.MapStorage{utils.MetaReq: passEvent} if pass, err := filterS.Pass("cgrates.org", []string{"*destinations:~*req.Destination:EU"}, fEv); err != nil { t.Errorf(err.Error()) diff --git a/loaders/libloader.go b/loaders/libloader.go index 1f06cd03b..645b4ab62 100644 --- a/loaders/libloader.go +++ b/loaders/libloader.go @@ -85,7 +85,7 @@ func (ld LoaderData) UpdateFromCSV(fileName string, record []string, // newCsvProvider constructs a DataProvider func newCsvProvider(record []string, fileName string) (dP utils.DataProvider) { - dP = &csvProvider{req: record, fileName: fileName, cache: config.NewNavigableMap(nil)} + dP = &csvProvider{req: record, fileName: fileName, cache: utils.MapStorage{}} return } @@ -93,7 +93,7 @@ func newCsvProvider(record []string, fileName string) (dP utils.DataProvider) { type csvProvider struct { req []string fileName string - cache *config.NavigableMap + cache utils.MapStorage } // String is part of utils.DataProvider interface @@ -116,7 +116,7 @@ func (cP *csvProvider) FieldAsInterface(fldPath []string) (data interface{}, err idx = fldPath[1] } if fileName != "" && cP.fileName != fileName { - cP.cache.Set(fldPath, nil, false, false) + cP.cache.Set(fldPath, nil) return } if cfgFieldIdx, err := strconv.Atoi(idx); err != nil || len(cP.req) <= cfgFieldIdx { @@ -125,7 +125,7 @@ func (cP *csvProvider) FieldAsInterface(fldPath []string) (data interface{}, err data = cP.req[cfgFieldIdx] } - cP.cache.Set(fldPath, data, false, false) + cP.cache.Set(fldPath, data) return } diff --git a/sessions/sessions.go b/sessions/sessions.go index fbb4ae215..dd9d96d1c 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -988,7 +988,7 @@ func (sS *SessionS) filterSessions(sf *utils.SessionFilter, psv bool) (aSs []*Ex return } var err error - ev := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: me.Data()}) + ev := utils.MapStorage{utils.MetaReq: me.Data()} for _, fltr := range filterRules { // we don't know how many values we have so we need to build the fieldValues DataProvider if pass, err = fltr.Pass(ev); err != nil || !pass { @@ -1044,7 +1044,7 @@ func (sS *SessionS) filterSessionsCount(sf *utils.SessionFilter, psv bool) (coun return } var err error - ev := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: me.Data()}) + ev := utils.MapStorage{utils.MetaReq: me.Data()} for _, fltr := range filterRules { // we don't know how many values we have so we need to build the fieldValues DataProvider if pass, err = fltr.Pass(ev); err != nil || !pass { diff --git a/utils/mapstorage.go b/utils/mapstorage.go new file mode 100644 index 000000000..4819cca3d --- /dev/null +++ b/utils/mapstorage.go @@ -0,0 +1,340 @@ +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package utils + +import ( + "errors" + "fmt" + "net" + "reflect" + "strings" + "time" +) + +// dataStorage is the DataProvider that can be updated +type dataStorage interface { + DataProvider + + Set(fldPath []string, val interface{}) error + Remove(fldPath []string) error + GetKeys(nesteed bool) []string +} + +// MapStorage is the basic dataStorage +type MapStorage map[string]interface{} + +// String returns the map as json string +func (ms MapStorage) String() string { return ToJSON(ms) } + +// FieldAsInterface returns the value from the path +func (ms MapStorage) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + err = errors.New("empty field path") + return + + } + opath, indx := GetPathIndex(fldPath[0]) + var has bool + if val, has = ms[opath]; !has { + err = ErrNotFound + return + } + if len(fldPath) == 1 { + if indx == nil { + return + + } + switch rv := val.(type) { + case []string: + if len(rv) <= *indx { + return nil, ErrNotFound + } + val = rv[*indx] + return + case []interface{}: + if len(rv) <= *indx { + return nil, ErrNotFound + } + val = rv[*indx] + return + default: + } + // only if all above fails use reflect: + vr := reflect.ValueOf(val) + if vr.Kind() == reflect.Ptr { + vr = vr.Elem() + } + if vr.Kind() != reflect.Slice && vr.Kind() != reflect.Array { + return nil, ErrNotFound + + } + if *indx >= vr.Len() { + return nil, ErrNotFound + } + return vr.Index(*indx).Interface(), nil + + } + if indx == nil { + switch dp := ms[fldPath[0]].(type) { + case DataProvider: + return dp.FieldAsInterface(fldPath[1:]) + case map[string]interface{}: + return MapStorage(dp).FieldAsInterface(fldPath[1:]) + default: + err = ErrWrongPath + + return + } + } + switch dp := ms[opath].(type) { + case []DataProvider: + if len(dp) <= *indx { + return nil, ErrNotFound + } + return dp[*indx].FieldAsInterface(fldPath[1:]) + case []MapStorage: + if len(dp) <= *indx { + return nil, ErrNotFound + } + return dp[*indx].FieldAsInterface(fldPath[1:]) + case []map[string]interface{}: + if len(dp) <= *indx { + return nil, ErrNotFound + + } + return MapStorage(dp[*indx]).FieldAsInterface(fldPath[1:]) + case []interface{}: + if len(dp) <= *indx { + return nil, ErrNotFound + } + switch ds := dp[*indx].(type) { + case DataProvider: + return ds.FieldAsInterface(fldPath[1:]) + case map[string]interface{}: + return MapStorage(ds).FieldAsInterface(fldPath[1:]) + default: + } + default: + + } + err = ErrNotFound // xml compatible + val = nil + return +} + +// FieldAsString returns the value from path as string +func (ms MapStorage) FieldAsString(fldPath []string) (str string, err error) { + var val interface{} + if val, err = ms.FieldAsInterface(fldPath); err != nil { + return + } + return IfaceAsString(val), nil +} + +// Set sets the value at the given path +func (ms MapStorage) Set(fldPath []string, val interface{}) (err error) { + if len(fldPath) == 0 { + return ErrWrongPath + } + if len(fldPath) == 1 { + ms[fldPath[0]] = val + + return + } + + if _, has := ms[fldPath[0]]; !has { + nMap := MapStorage{} + ms[fldPath[0]] = nMap + return nMap.Set(fldPath[1:], val) + } + switch dp := ms[fldPath[0]].(type) { + case dataStorage: + return dp.Set(fldPath[1:], val) + case map[string]interface{}: + return MapStorage(dp).Set(fldPath[1:], val) + default: + return ErrWrongPath + } + +} + +// GetKeys returns all the keys from map +func (ms MapStorage) GetKeys(nesteed bool) (keys []string) { + if !nesteed { + keys = make([]string, len(ms)) + i := 0 + for k := range ms { + keys[i] = k + i++ + + } + return + } + for k, v := range ms { + keys = append(keys, k) + switch rv := v.(type) { + case dataStorage: + for _, dsKey := range rv.GetKeys(nesteed) { + keys = append(keys, k+NestingSep+dsKey) + } + case map[string]interface{}: + for _, dsKey := range MapStorage(rv).GetKeys(nesteed) { + keys = append(keys, k+NestingSep+dsKey) + } + case []MapStorage: + for i, dp := range rv { + pref := k + fmt.Sprintf("[%v]", i) + keys = append(keys, pref) + for _, dsKey := range dp.GetKeys(nesteed) { + keys = append(keys, pref+NestingSep+dsKey) + } + } + case []dataStorage: + for i, dp := range rv { + pref := k + fmt.Sprintf("[%v]", i) + keys = append(keys, pref) + for _, dsKey := range dp.GetKeys(nesteed) { + keys = append(keys, pref+NestingSep+dsKey) + } + } + case []map[string]interface{}: + for i, dp := range rv { + pref := k + fmt.Sprintf("[%v]", i) + keys = append(keys, pref) + for _, dsKey := range MapStorage(dp).GetKeys(nesteed) { + keys = append(keys, pref+NestingSep+dsKey) + } + } + case []interface{}: + for i := range rv { + keys = append(keys, k+fmt.Sprintf("[%v]", i)) + } + case []string: + for i := range rv { + keys = append(keys, k+fmt.Sprintf("[%v]", i)) + } + default: + // ToDo:should not be called + keys = append(keys, getPathFromInterface(v, k+NestingSep)...) + } + + } + return + +} + +// Remove removes the item at path +func (ms MapStorage) Remove(fldPath []string) (err error) { + if len(fldPath) == 0 { + return ErrWrongPath + } + var val interface{} + var has bool + if val, has = ms[fldPath[0]]; !has { + return // ignore (already removed) + } + if len(fldPath) == 1 { + delete(ms, fldPath[0]) + + return + } + switch dp := val.(type) { + case dataStorage: + return dp.Remove(fldPath[1:]) + case map[string]interface{}: + return MapStorage(dp).Remove(fldPath[1:]) + default: + return ErrWrongPath + + } + +} + +// RemoteHost is part of dataStorage interface +func (ms MapStorage) RemoteHost() net.Addr { + return LocalAddr() +} + +// ToDo: remove the following functions +func getPathFromValue(in reflect.Value, prefix string) (out []string) { + switch in.Kind() { + case reflect.Ptr: + return getPathFromValue(in.Elem(), prefix) + case reflect.Array, reflect.Slice: + prefix = strings.TrimSuffix(prefix, NestingSep) + for i := 0; i < in.Len(); i++ { + pref := fmt.Sprintf("%s[%v]", prefix, i) + out = append(out, pref) + out = append(out, getPathFromValue(in.Index(i), pref+NestingSep)...) + } + case reflect.Map: + iter := reflect.ValueOf(in).MapRange() + for iter.Next() { + pref := prefix + iter.Key().String() + out = append(out, pref) + out = append(out, getPathFromValue(iter.Value(), pref+NestingSep)...) + } + case reflect.Struct: + inType := in.Type() + for i := 0; i < in.NumField(); i++ { + pref := prefix + inType.Field(i).Name + out = append(out, pref) + out = append(out, getPathFromValue(in.Field(i), pref+NestingSep)...) + } + case reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String, reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Interface: + default: + } + return +} + +// used by MapStorage2 GetKeys to return all values +func getPathFromInterface(in interface{}, prefix string) (out []string) { + switch vin := in.(type) { + case map[string]interface{}: + for k, val := range vin { + pref := prefix + k + out = append(out, pref) + out = append(out, getPathFromInterface(val, pref+NestingSep)...) + } + case []map[string]interface{}: + prefix = strings.TrimSuffix(prefix, NestingSep) + for i, val := range vin { + pref := fmt.Sprintf("%s[%v]", prefix, i) + out = append(out, pref) + out = append(out, getPathFromInterface(val, pref+NestingSep)...) + } + case []interface{}: + prefix = strings.TrimSuffix(prefix, NestingSep) + for i, val := range vin { + pref := fmt.Sprintf("%s[%v]", prefix, i) + out = append(out, pref) + out = append(out, getPathFromInterface(val, pref+NestingSep)...) + } + case []string: + prefix = strings.TrimSuffix(prefix, NestingSep) + for i := range vin { + pref := fmt.Sprintf("%s[%v]", prefix, i) + out = append(out, pref) + } + case nil, int, int32, int64, uint32, uint64, bool, float32, float64, []uint8, time.Duration, time.Time, string: //no path + default: //reflect based + out = getPathFromValue(reflect.ValueOf(vin), prefix) + } + return +} diff --git a/utils/mapstorage_test.go b/utils/mapstorage_test.go new file mode 100644 index 000000000..a462f354f --- /dev/null +++ b/utils/mapstorage_test.go @@ -0,0 +1,348 @@ +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ +package utils + +import ( + "reflect" + "sort" + "strings" + "testing" +) + +func TestNavMapGetFieldAsString(t *testing.T) { + nM := MapStorage{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": "Val1", + }, + }, + }, + "AnotherFirstLevel": "ValAnotherFirstLevel", + } + eVal := "Val1" + if strVal, err := nM.FieldAsString( + strings.Split("FirstLevel>SecondLevel>ThirdLevel>Fld1", ">")); err != nil { + t.Error(err) + } else if strVal != eVal { + t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) + } + eVal = "ValAnotherFirstLevel" + if strVal, err := nM.FieldAsString( + strings.Split("AnotherFirstLevel", ">")); err != nil { + t.Error(err) + } else if strVal != eVal { + t.Errorf("expecting: <%s> received: <%s>", eVal, strVal) + } + fPath := "NonExisting>AnotherFirstLevel" + if _, err := nM.FieldAsString(strings.Split(fPath, ">")); err.Error() != + ErrNotFound.Error() { + t.Error(err) + } +} + +type myEv map[string]interface{} + +func (ev myEv) AsMapStorage() (MapStorage, error) { + return MapStorage(ev), nil +} + +func TestNavMapAsMapStorage(t *testing.T) { + myData := myEv{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": 123.123, + }, + }, + }, + "FistLever2": map[string]interface{}{ + "SecondLevel2": map[string]interface{}{ + "Field2": 123, + }, + "Field3": "Value3", + }, + "Field4": &testStruct{ + Item1: "Ten", + Item2: 10, + }, + } + + eNavMap := MapStorage{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": 123.123, + }, + }, + }, + "FistLever2": map[string]interface{}{ + "SecondLevel2": map[string]interface{}{ + "Field2": 123, + }, + "Field3": "Value3", + }, + "Field4": &testStruct{ + Item1: "Ten", + Item2: 10, + }, + } + + if rcv, err := myData.AsMapStorage(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eNavMap, rcv) { + t.Errorf("Expecting: %+v, received: %+v", eNavMap, rcv) + } +} + +type testStruct struct { + Item1 string + Item2 int +} + +func TestNavMapAdd2(t *testing.T) { + nM := MapStorage{} + path := []string{"FistLever2", "SecondLevel2", "Field2"} + data := 123 + nM.Set(path, data) + path = []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1"} + data1 := 123.123 + nM.Set(path, data1) + path = []string{"FistLever2", "Field3"} + data2 := "Value3" + nM.Set(path, data2) + path = []string{"Field4"} + data3 := &testStruct{ + Item1: "Ten", + Item2: 10, + } + nM.Set(path, data3) + eNavMap := MapStorage{ + "FirstLevel": MapStorage{ + "SecondLevel": MapStorage{ + "ThirdLevel": MapStorage{ + "Fld1": 123.123, + }, + }, + }, + "FistLever2": MapStorage{ + "SecondLevel2": MapStorage{ + "Field2": 123, + }, + "Field3": "Value3", + }, + "Field4": &testStruct{ + Item1: "Ten", + Item2: 10, + }, + } + if !reflect.DeepEqual(nM, eNavMap) { + t.Errorf("Expecting: %+v, received: %+v", eNavMap, nM) + } +} + +func TestNavMapString(t *testing.T) { + myData := map[string]interface{}{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": "Val1", + }, + }, + }, + "FistLever2": map[string]interface{}{ + "SecondLevel2": map[string]interface{}{ + "Field2": "Value2", + }, + "Field3": "Value3", + }, + "Field4": "Val4", + } + nM := MapStorage(myData) + eStr := ToJSON(myData) + if !reflect.DeepEqual(nM.String(), eStr) { + t.Errorf("Expecting: %+v, received: %+v", eStr, nM.String()) + } +} + +func TestNavMapGetField(t *testing.T) { + nM := MapStorage{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": []interface{}{"Val1", "Val2"}, + }, + }, + }, + "FirstLevel2": map[string]interface{}{ + "SecondLevel2": []map[string]interface{}{ + map[string]interface{}{ + "ThirdLevel2": map[string]interface{}{ + "Fld1": "Val1", + }, + }, + map[string]interface{}{ + "Count": 10, + "ThirdLevel2": map[string]interface{}{ + "Fld2": []string{"Val1", "Val2", "Val3"}, + }, + }, + }, + }, + "AnotherFirstLevel": "ValAnotherFirstLevel", + } + pth := []string{"FirstLevel", "SecondLevel", "ThirdLevel", "Fld1[0]"} + eFld := "Val1" + if fld, err := nM.FieldAsInterface(pth); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eFld, fld) { + t.Errorf("expecting: %s, received: %s", ToIJSON(eFld), ToIJSON(fld)) + } + eFld2 := map[string]interface{}{"Fld1": "Val1"} + pth = []string{"FirstLevel2", "SecondLevel2[0]", "ThirdLevel2"} + if fld, err := nM.FieldAsInterface(pth); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eFld2, fld) { + t.Errorf("expecting: %s, received: %s", ToIJSON(eFld2), ToIJSON(fld)) + } + eFld3 := "ValAnotherFirstLevel" + pth = []string{"AnotherFirstLevel"} + if fld, err := nM.FieldAsInterface(pth); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eFld3, fld) { + t.Errorf("expecting: %s, received: %s", ToIJSON(eFld3), ToIJSON(fld)) + } + pth = []string{"AnotherFirstLevel2"} + if _, err := nM.FieldAsInterface(pth); err == nil || err != ErrNotFound { + t.Error(err) + } + pth = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel", "Fld1[0]"} + if _, err := nM.FieldAsInterface(pth); err == nil || err != ErrNotFound { + t.Error(err) + } +} + +func TestNavMapFieldAsInterface(t *testing.T) { + nM := MapStorage{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": []map[string]interface{}{ + map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": "Val1", + }, + }, + map[string]interface{}{ + "Count": 10, + "ThirdLevel2": map[string]interface{}{ + "Fld2": []string{"Val1", "Val2", "Val3"}, + }, + }, + }, + }, + "AnotherFirstLevel": "ValAnotherFirstLevel", + } + + path := []string{"FirstLevel", "SecondLevel[0]", "Count"} + expErr := ErrNotFound + var eVal interface{} = nil + if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() { + t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err) + } + + path = []string{"AnotherFirstLevel", "SecondLevel", "Count"} + expErr = ErrWrongPath + if _, err := nM.FieldAsInterface(path); err != nil && err.Error() != expErr.Error() { + t.Errorf("Expected error: %s, received error: %v", expErr.Error(), err) + } + + path = []string{"FirstLevel", "SecondLevel[1]", "Count"} + eVal = 10 + if rplyVal, err := nM.FieldAsInterface(path); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eVal, rplyVal) { + t.Errorf("Expected: %s , received: %s", ToJSON(eVal), ToJSON(rplyVal)) + } + + path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2"} + eVal = []string{"Val1", "Val2", "Val3"} + if rplyVal, err := nM.FieldAsInterface(path); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eVal, rplyVal) { + t.Errorf("Expected: %s , received: %s", ToJSON(eVal), ToJSON(rplyVal)) + } + + path = []string{"FirstLevel", "SecondLevel[1]", "ThirdLevel2", "Fld2[2]"} + eVal = "Val3" + if rplyVal, err := nM.FieldAsInterface(path); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eVal, rplyVal) { + t.Errorf("Expected: %s , received: %s", ToJSON(eVal), ToJSON(rplyVal)) + } +} + +func TestNavMapGetKeys(t *testing.T) { + navMp := MapStorage{ + "FirstLevel": map[string]interface{}{ + "SecondLevel": map[string]interface{}{ + "ThirdLevel": map[string]interface{}{ + "Fld1": 123.123, + }, + }, + }, + "FistLever2": map[string]interface{}{ + "SecondLevel2": map[string]interface{}{ + "Field2": 123, + }, + "Field3": "Value3", + "Field4": &testStruct{ + Item1: "Ten", + Item2: 10, + }, + }, + "Field5": &testStruct{ + Item1: "Ten", + Item2: 10, + }, + "Field6": []string{"1", "2"}, + } + expKeys := []string{ + "FirstLevel", + "FirstLevel.SecondLevel", + "FirstLevel.SecondLevel.ThirdLevel", + "FirstLevel.SecondLevel.ThirdLevel.Fld1", + "FistLever2", + "FistLever2.SecondLevel2", + "FistLever2.SecondLevel2.Field2", + "FistLever2.Field3", + "FistLever2.Field4", + "FistLever2.Field4.Item1", + "FistLever2.Field4.Item2", + "Field5", + "Field5.Item1", + "Field5.Item2", + "Field6", + "Field6[0]", + "Field6[1]", + } + keys := navMp.GetKeys(true) + sort.Strings(expKeys) + sort.Strings(keys) + if !reflect.DeepEqual(expKeys, keys) { + t.Errorf("Expecting: %+v, received: %+v", ToJSON(expKeys), ToJSON(keys)) + } +}