From 8dbd7ea2cdf35b5f538787db8c045ac5f9b5d63b Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 31 Jan 2020 10:29:26 +0100 Subject: [PATCH] Initial AgentRequest with SetFields --- agents/agentreq.go | 53 ++++++++++++++++++--------- config/navigablemap.go | 53 +++++++++++++++++++++++++++ config/navigablemap_test.go | 71 +++++++++++++++++++++++++++++++++++++ utils/consts.go | 1 + 4 files changed, 161 insertions(+), 17 deletions(-) diff --git a/agents/agentreq.go b/agents/agentreq.go index fc96be678..1aca6a7cd 100644 --- a/agents/agentreq.go +++ b/agents/agentreq.go @@ -83,6 +83,7 @@ type AgentRequest struct { filterS *engine.FilterS Header config.DataProvider Trailer config.DataProvider + diamreq *config.NavigableMap // used in case of building requests (ie. DisconnectSession) } // String implements engine.DataProvider @@ -103,23 +104,18 @@ func (ar *AgentRequest) FieldAsInterface(fldPath []string) (val interface{}, err case utils.MetaReq: val, err = ar.Request.FieldAsInterface(fldPath[1:]) case utils.MetaVars: - val, err = ar.Vars.FieldAsInterface(fldPath[1:]) + val, err = ar.Vars.GetField(fldPath[1:]) case utils.MetaCgreq: - val, err = ar.CGRRequest.FieldAsInterface(fldPath[1:]) + val, err = ar.CGRRequest.GetField(fldPath[1:]) case utils.MetaCgrep: - val, err = ar.CGRReply.FieldAsInterface(fldPath[1:]) + val, err = ar.CGRReply.GetField(fldPath[1:]) case utils.MetaRep: - val, err = ar.Reply.FieldAsInterface(fldPath[1:]) - case utils.MetaCGRAReq: - val, err = ar.CGRAReq.FieldAsInterface(fldPath[1:]) + val, err = ar.Reply.GetField(fldPath[1:]) case utils.MetaHdr: val, err = ar.Header.FieldAsInterface(fldPath[1:]) case utils.MetaTrl: val, err = ar.Trailer.FieldAsInterface(fldPath[1:]) } - if nmItems, isNMItems := val.([]*config.NMItem); isNMItems { // special handling of NMItems, take the last value out of it - val = nmItems[len(nmItems)-1].Data // could be we need nil protection here - } return } @@ -129,17 +125,24 @@ func (ar *AgentRequest) FieldAsString(fldPath []string) (val string, err error) if iface, err = ar.FieldAsInterface(fldPath); err != nil { return } + if nmItems, isNMItems := iface.([]*config.NMItem); isNMItems { // special handling of NMItems, take the last value out of it + iface = nmItems[len(nmItems)-1].Data // could be we need nil protection here + } return utils.IfaceAsString(iface), nil } // AsNavigableMap implements engine.DataProvider func (ar *AgentRequest) AsNavigableMap(tplFlds []*config.FCTemplate) ( nM *config.NavigableMap, err error) { - ar.CGRAReq = config.NewNavigableMap(nil) + return nil, utils.ErrNotImplemented +} + +//SetFields will populate fields of AgentRequest out of templates +func (ar *AgentRequest) SetFields(tplFlds []*config.FCTemplate) (err error) { for _, tplFld := range tplFlds { if pass, err := ar.filterS.Pass(ar.Tenant, tplFld.Filters, ar); err != nil { - return nil, err + return err } else if !pass { continue } @@ -153,15 +156,15 @@ func (ar *AgentRequest) AsNavigableMap(tplFlds []*config.FCTemplate) ( } err = utils.ErrPrefixNotFound(tplFld.Tag) } - return nil, err + return err } var valSet []*config.NMItem fldPath := strings.Split(tplFld.Path, utils.NestingSep) - nMItm := &config.NMItem{Data: out, Path: fldPath, Config: tplFld} - if nMFields, err := ar.CGRAReq.FieldAsInterface(fldPath); err != nil { + nMItm := &config.NMItem{Data: out, Path: fldPath[1:], Config: tplFld} + if nMFields, err := ar.FieldAsInterface(fldPath); err != nil { if err != utils.ErrNotFound { - return nil, err + return err } } else { valSet = nMFields.([]*config.NMItem) // start from previous stored fields @@ -173,13 +176,29 @@ func (ar *AgentRequest) AsNavigableMap(tplFlds []*config.FCTemplate) ( valSet = valSet[:len(valSet)-1] // discard the last item } valSet = append(valSet, nMItm) - ar.CGRAReq.Set(fldPath, valSet, false, true) + switch fldPath[0] { + default: + return fmt.Errorf("unsupported field prefix: <%s> when set fields", fldPath[0]) + case utils.MetaVars: + ar.Vars.Set(fldPath[1:], valSet, false, true) + case utils.MetaCgreq: + ar.CGRRequest.Set(fldPath[1:], valSet, false, true) + case utils.MetaCgrep: + ar.CGRReply.Set(fldPath[1:], valSet, false, true) + case utils.MetaRep: + ar.Reply.Set(fldPath[1:], valSet, false, true) + case utils.MetaDiamreq: + if ar.diamreq == nil { + ar.diamreq = config.NewNavigableMap(nil) // special case when CGRateS is building the request + } + ar.diamreq.Set(fldPath[1:], valSet, false, true) + } } if tplFld.Blocker { // useful in case of processing errors first break } } - return ar.CGRAReq, nil + return } // ParseField outputs the value based on the template item diff --git a/config/navigablemap.go b/config/navigablemap.go index 2e283e7d9..687608f33 100644 --- a/config/navigablemap.go +++ b/config/navigablemap.go @@ -93,6 +93,59 @@ func (nM *NavigableMap) Set(path []string, data interface{}, apnd, ordered bool) } } +// 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) + } + if lastMp, err = nM.getNextMap(lastMp, spath); err != nil { + return + } + } + 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 DataProvider // supports spath with selective elements in case of []*NMItem diff --git a/config/navigablemap_test.go b/config/navigablemap_test.go index c7caf898f..6ac2a28eb 100644 --- a/config/navigablemap_test.go +++ b/config/navigablemap_test.go @@ -997,6 +997,77 @@ func TestNavMapgetLastItem(t *testing.T) { } } +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{}{ diff --git a/utils/consts.go b/utils/consts.go index e54110134..df676bd51 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -621,6 +621,7 @@ const ( MaxCost = "MaxCost" MetaLoaders = "*loaders" TmpSuffix = ".tmp" + MetaDiamreq = "*diamreq" ) // Migrator Action