diff --git a/utils/consts.go b/utils/consts.go
index 802749ae9..9ce413acb 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -525,6 +525,9 @@ const (
Event = "Event"
EmptyString = ""
AgentRequest = "AgentRequest"
+ DynamicDataPrefix = "~"
+ AttrValueSep = "="
+ ANDSep = "&"
)
// Migrator Action
diff --git a/utils/dataconverter.go b/utils/dataconverter.go
index 374b494c8..1f6f7e084 100644
--- a/utils/dataconverter.go
+++ b/utils/dataconverter.go
@@ -80,6 +80,14 @@ func NewDataConverter(params string) (
}
}
+func NewDataConverterMustCompile(params string) (conv DataConverter) {
+ var err error
+ if conv, err = NewDataConverter(params); err != nil {
+ panic(fmt.Sprintf("parsing: <%s>, error: %s", params, err.Error()))
+ }
+ return
+}
+
func NewDurationSecondsConverter(params string) (
hdlr DataConverter, err error) {
return new(DurationSecondsConverter), nil
diff --git a/utils/rsrfield.go b/utils/rsrfield.go
index c1c003d72..044266f26 100644
--- a/utils/rsrfield.go
+++ b/utils/rsrfield.go
@@ -218,6 +218,15 @@ func NewRSRFilter(fltrVal string) (rsrFltr *RSRFilter, err error) {
return rsrFltr, nil
}
+// NewRSRFilterMustCompile is used mostly in tests
+func NewRSRFilterMustCompile(fltrVal string) (rsrFltr *RSRFilter) {
+ var err error
+ if rsrFltr, err = NewRSRFilter(fltrVal); err != nil {
+ panic(fmt.Sprintf("parsing <%s>, err: %s", fltrVal, err.Error()))
+ }
+ return
+}
+
// One filter rule
type RSRFilter struct {
filterRule string // Value in raw format
diff --git a/utils/rsrfield_test.go b/utils/rsrfield_test.go
index d2253a47d..3e4af5b28 100644
--- a/utils/rsrfield_test.go
+++ b/utils/rsrfield_test.go
@@ -71,11 +71,6 @@ func TestNewRSRField1(t *testing.T) {
} else if !reflect.DeepEqual(expRSRField, rsrField) {
t.Errorf("Expecting: %+v, received: %+v", expRSRField, rsrField)
}
- // Separator escaped
- rulesStr = `~sip_redirected_to:s\/sip:\+49(\d+)@/0$1/`
- if rsrField, err := NewRSRField(rulesStr); err == nil {
- t.Errorf("Parse error, field rule does not contain correct number of separators, received: %v", rsrField)
- }
// One extra separator but escaped
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)\/@/0$1/`
expRSRField3 := &RSRField{Id: "sip_redirected_to", Rules: rulesStr,
diff --git a/utils/rsrparser.go b/utils/rsrparser.go
new file mode 100644
index 000000000..07b749407
--- /dev/null
+++ b/utils/rsrparser.go
@@ -0,0 +1,135 @@
+/*
+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 (
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+func NewRSRParser(parserRules, inRuleSep string) (rsrParser *RSRParser, err error) {
+ if len(parserRules) == 0 {
+ return
+ }
+ rsrParser = &RSRParser{Rules: parserRules}
+ if strings.HasSuffix(parserRules, FILTER_VAL_END) { // Has filter, populate the var
+ fltrStart := strings.LastIndex(parserRules, FILTER_VAL_START)
+ if fltrStart < 1 {
+ return nil, fmt.Errorf("invalid RSRFilter start rule in string: <%s>", parserRules)
+ }
+ fltrVal := parserRules[fltrStart+1 : len(parserRules)-1]
+ rsrParser.filters, err = ParseRSRFilters(fltrVal, inRuleSep)
+ if err != nil {
+ return nil, fmt.Errorf("Invalid FilterValue in string: %s, err: %s", fltrVal, err.Error())
+ }
+ parserRules = parserRules[:fltrStart] // Take the filter part out before compiling further
+ }
+ if idxConverters := strings.Index(parserRules, "{*"); idxConverters != -1 { // converters in the string
+ if !strings.HasSuffix(parserRules, "}") {
+ return nil,
+ fmt.Errorf("invalid converter terminator in rule: <%s>",
+ parserRules)
+ }
+ convertersStr := parserRules[idxConverters+1 : len(parserRules)-1] // strip also {}
+ convsSplt := strings.Split(convertersStr, inRuleSep)
+ rsrParser.converters = make(DataConverters, len(convsSplt))
+ for i, convStr := range convsSplt {
+ if conv, err := NewDataConverter(convStr); err != nil {
+ return nil,
+ fmt.Errorf("invalid converter value in string: <%s>, err: %s",
+ convStr, err.Error())
+ } else {
+ rsrParser.converters[i] = conv
+ }
+ }
+ parserRules = parserRules[:idxConverters]
+ }
+ if !strings.HasPrefix(parserRules, DynamicDataPrefix) { // special case when RSR is defined as static attribute=value
+ var staticHdr, staticVal string
+ if splt := strings.Split(parserRules, AttrValueSep); len(splt) == 2 { // using '='' as separator since ':' is often use in date/time fields
+ staticHdr, staticVal = splt[0], splt[1] // strip the separator
+ if strings.HasSuffix(staticVal, AttrValueSep) { // if value ends with sep, strip it since it is a part of the definition syntax
+ staticVal = staticVal[:len(staticVal)-1]
+ }
+ } else if len(splt) > 2 {
+ return nil, fmt.Errorf("invalid RSRField static rules: <%s>", parserRules)
+ } else {
+ staticVal = splt[0] // no attribute name
+ }
+ rsrParser.attrName = staticHdr
+ rsrParser.attrValue = staticVal
+ return
+ }
+ // dynamic content via attributeNames
+ spltRgxp := regexp.MustCompile(`:s\/`)
+ spltRules := spltRgxp.Split(parserRules, -1)
+ rsrParser.attrName = spltRules[0][1:] // in form ~hdr_name
+ if len(spltRules) > 1 {
+ rulesRgxp := regexp.MustCompile(`(?:(.+[^\\])\/(.*[^\\])*\/){1,}`)
+ for _, ruleStr := range spltRules[1:] { // :s/ already removed through split
+ fmt.Printf("ruleStr: %s\n", ruleStr)
+ allMatches := rulesRgxp.FindStringSubmatch(ruleStr)
+ if len(allMatches) != 3 {
+ return nil, fmt.Errorf("not enough members in Search&Replace, ruleStr: <%s>, matches: %v, ", ruleStr, allMatches)
+ }
+ if srRegexp, err := regexp.Compile(allMatches[1]); err != nil {
+ return nil, fmt.Errorf("invalid Search&Replace subfield rule: <%s>", allMatches[1])
+ } else {
+ rsrParser.rsrRules = append(rsrParser.rsrRules, &ReSearchReplace{SearchRegexp: srRegexp, ReplaceTemplate: allMatches[2]})
+ }
+ }
+ }
+ return
+}
+
+// RSRParser is a parser for data coming from various sources
+type RSRParser struct {
+ Rules string // Rules container holding the string rules, public so it can be stored
+
+ attrName string // instruct extracting info out of header in event
+ attrValue string // if populated, enforces parsing always to this value
+ rsrRules []*ReSearchReplace // rules to use when parsing value
+ converters DataConverters // set of converters to apply on output
+ filters RSRFilters // The value to compare when used as filter
+}
+
+func NewRSRParsers(parsersRules, rlsSep, inRlSep string) (prsrs RSRParsers, err error) {
+ if parsersRules == "" {
+ return
+ }
+ return NewRSRParsersFromSlice(strings.Split(parsersRules, rlsSep), inRlSep)
+}
+
+func NewRSRParsersFromSlice(parsersRules []string, inRlSep string) (prsrs RSRParsers, err error) {
+ prsrs = make(RSRParsers, len(parsersRules))
+ for i, rlStr := range parsersRules {
+ if rsrPrsr, err := NewRSRParser(rlStr, inRlSep); err != nil {
+ return nil, err
+ } else if rsrPrsr == nil {
+ return nil, fmt.Errorf("emtpy RSRParser in rule: <%s>", rlStr)
+ } else {
+ prsrs[i] = rsrPrsr
+ }
+ }
+ return
+}
+
+// RSRParsers is a set of RSRParser
+type RSRParsers []*RSRParser
diff --git a/utils/rsrparser_test.go b/utils/rsrparser_test.go
new file mode 100644
index 000000000..b4bdeda5d
--- /dev/null
+++ b/utils/rsrparser_test.go
@@ -0,0 +1,56 @@
+/*
+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"
+ "regexp"
+ "testing"
+)
+
+func TestNewRSRParsers(t *testing.T) {
+ ruleStr := `Value1;Heade2=Value2;~Header3(Val3&!Val4);~Header4:s/^a/${1}b/{*duration_seconds&*round:2}(b&c);Value5{*duration_seconds&*round:2}`
+ eRSRParsers := RSRParsers{
+ &RSRParser{Rules: "Value1", attrValue: "Value1"},
+ &RSRParser{Rules: "Heade2=Value2", attrName: "Heade2", attrValue: "Value2"},
+ &RSRParser{Rules: "~Header3(Val3&!Val4)", attrName: "Header3",
+ filters: RSRFilters{NewRSRFilterMustCompile("Val3"),
+ NewRSRFilterMustCompile("!Val4")}},
+
+ &RSRParser{Rules: "~Header4:s/^a/${1}b/{*duration_seconds&*round:2}(b&c)", attrName: "Header4",
+ rsrRules: []*ReSearchReplace{
+ &ReSearchReplace{
+ SearchRegexp: regexp.MustCompile(`^a`),
+ ReplaceTemplate: "${1}b"}},
+ converters: DataConverters{NewDataConverterMustCompile("*duration_seconds"),
+ NewDataConverterMustCompile("*round:2")},
+ filters: RSRFilters{NewRSRFilterMustCompile("b"),
+ NewRSRFilterMustCompile("c")},
+ },
+
+ &RSRParser{Rules: "Value5{*duration_seconds&*round:2}", attrValue: "Value5",
+ converters: DataConverters{NewDataConverterMustCompile("*duration_seconds"),
+ NewDataConverterMustCompile("*round:2")},
+ },
+ }
+ if rsrParsers, err := NewRSRParsers(ruleStr, INFIELD_SEP, ANDSep); err != nil {
+ t.Error("Unexpected error: ", err.Error())
+ } else if !reflect.DeepEqual(eRSRParsers, rsrParsers) {
+ t.Errorf("expecting: %+v, received: %+v", eRSRParsers, rsrParsers)
+ }
+}