mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Replaced RSRField with RSRParser
This commit is contained in:
committed by
Dan Christian Bogos
parent
ffcb95e2ca
commit
6fd40f7296
@@ -25,9 +25,9 @@ import (
|
||||
)
|
||||
|
||||
type CdrsCfg struct {
|
||||
Enabled bool // Enable CDR Server service
|
||||
ExtraFields []*utils.RSRField // Extra fields to store in CDRs
|
||||
StoreCdrs bool // store cdrs in storDb
|
||||
Enabled bool // Enable CDR Server service
|
||||
ExtraFields RSRParsers // Extra fields to store in CDRs
|
||||
StoreCdrs bool // store cdrs in storDb
|
||||
SMCostRetries int
|
||||
ChargerSConns []string
|
||||
RaterConns []string
|
||||
@@ -48,7 +48,7 @@ func (cdrscfg *CdrsCfg) loadFromJsonCfg(jsnCdrsCfg *CdrsJsonCfg) (err error) {
|
||||
cdrscfg.Enabled = *jsnCdrsCfg.Enabled
|
||||
}
|
||||
if jsnCdrsCfg.Extra_fields != nil {
|
||||
if cdrscfg.ExtraFields, err = utils.ParseRSRFieldsFromSlice(*jsnCdrsCfg.Extra_fields); err != nil {
|
||||
if cdrscfg.ExtraFields, err = NewRSRParsersFromSlice(*jsnCdrsCfg.Extra_fields); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestCdrsCfgloadFromJsonCfg(t *testing.T) {
|
||||
OnlineCDRExports: []string{"randomVal"},
|
||||
SchedulerConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaScheduler), "*conn1"},
|
||||
EEsConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaEEs), "*conn1"},
|
||||
ExtraFields: RSRParsers{},
|
||||
}
|
||||
if jsnCfg, err := NewDefaultCGRConfig(); err != nil {
|
||||
t.Error(err)
|
||||
@@ -64,7 +65,7 @@ func TestExtraFieldsinloadFromJsonCfg(t *testing.T) {
|
||||
cfgJSON := &CdrsJsonCfg{
|
||||
Extra_fields: &[]string{utils.EmptyString},
|
||||
}
|
||||
expectedErrMessage := "Empty RSRField in rule: "
|
||||
expectedErrMessage := "emtpy RSRParser in rule: <>"
|
||||
if jsonCfg, err := NewDefaultCGRConfig(); err != nil {
|
||||
t.Error(err)
|
||||
} else if err = jsonCfg.cdrsCfg.loadFromJsonCfg(cfgJSON); err == nil || err.Error() != expectedErrMessage {
|
||||
@@ -76,7 +77,7 @@ func TestCdrsCfgAsMapInterface(t *testing.T) {
|
||||
cfgJSONStr := `{
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"extra_fields": ["PayPalAccount", "LCRProfile", "ResourceID"],
|
||||
"extra_fields": ["~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"],
|
||||
"store_cdrs": true,
|
||||
"session_cost_retries": 5,
|
||||
"chargers_conns":["*internal:*chargers","*conn1"],
|
||||
@@ -91,7 +92,7 @@ func TestCdrsCfgAsMapInterface(t *testing.T) {
|
||||
}`
|
||||
eMap := map[string]interface{}{
|
||||
utils.EnabledCfg: true,
|
||||
utils.ExtraFieldsCfg: []string{"PayPalAccount", "LCRProfile", "ResourceID"},
|
||||
utils.ExtraFieldsCfg: []string{"~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"},
|
||||
utils.StoreCdrsCfg: true,
|
||||
utils.SessionCostRetires: 5,
|
||||
utils.ChargerSConnsCfg: []string{utils.MetaInternal, "*conn1"},
|
||||
@@ -111,7 +112,7 @@ func TestCdrsCfgAsMapInterface(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCdrsCfgAsMapInterface2(t *testing.T) {
|
||||
cfgJsonStr := `{
|
||||
cfgJSONStr := `{
|
||||
"cdrs": {
|
||||
"enabled":true,
|
||||
"chargers_conns": ["conn1", "conn2"],
|
||||
@@ -133,7 +134,7 @@ func TestCdrsCfgAsMapInterface2(t *testing.T) {
|
||||
utils.SchedulerConnsCfg: []string{},
|
||||
utils.EEsConnsCfg: []string{"conn1"},
|
||||
}
|
||||
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJsonStr); err != nil {
|
||||
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSONStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if rcv := cgrCfg.cdrsCfg.AsMapInterface(); !reflect.DeepEqual(rcv, eMap) {
|
||||
t.Errorf("Expected %+v \n, recieved %+v", eMap, rcv)
|
||||
|
||||
@@ -328,13 +328,13 @@ func testCGRConfigReloadCDRs(t *testing.T) {
|
||||
} else if reply != utils.OK {
|
||||
t.Errorf("Expected OK received: %s", reply)
|
||||
}
|
||||
rsr, err := NewRSRParsersFromSlice([]string{"~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expAttr := &CdrsCfg{
|
||||
Enabled: true,
|
||||
ExtraFields: utils.RSRFields{
|
||||
utils.NewRSRFieldMustCompile("PayPalAccount"),
|
||||
utils.NewRSRFieldMustCompile("LCRProfile"),
|
||||
utils.NewRSRFieldMustCompile("ResourceID"),
|
||||
},
|
||||
Enabled: true,
|
||||
ExtraFields: rsr,
|
||||
ChargerSConns: []string{utils.MetaLocalHost},
|
||||
RaterConns: []string{},
|
||||
AttributeSConns: []string{},
|
||||
|
||||
@@ -430,6 +430,7 @@ func TestCgrCfgJSONDefaultsCDRS(t *testing.T) {
|
||||
StatSConns: []string{},
|
||||
SchedulerConns: []string{},
|
||||
EEsConns: []string{},
|
||||
ExtraFields: RSRParsers{},
|
||||
}
|
||||
if !reflect.DeepEqual(eCdrsCfg, cgrCfg.cdrsCfg) {
|
||||
t.Errorf("Expecting: %+v , received: %+v", eCdrsCfg, cgrCfg.cdrsCfg)
|
||||
|
||||
@@ -587,3 +587,301 @@ func TestRSRParsersGetIfaceFromValues(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRSRParser(t *testing.T) {
|
||||
// Normal case
|
||||
rulesStr := `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/`
|
||||
expRSRField1 := &RSRParser{
|
||||
path: "~sip_redirected_to",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{
|
||||
{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`),
|
||||
ReplaceTemplate: "0$1",
|
||||
},
|
||||
},
|
||||
converters: nil,
|
||||
}
|
||||
if rsrField, err := NewRSRParser(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField1, rsrField) {
|
||||
t.Errorf("Expecting: %+v, received: %+v",
|
||||
expRSRField1, rsrField)
|
||||
}
|
||||
|
||||
// with dataConverters
|
||||
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/{*duration_seconds&*round:5:*middle}`
|
||||
expRSRField := &RSRParser{
|
||||
path: "~sip_redirected_to",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`),
|
||||
ReplaceTemplate: "0$1",
|
||||
}},
|
||||
converters: []utils.DataConverter{
|
||||
new(utils.DurationSecondsConverter),
|
||||
&utils.RoundConverter{Decimals: 5, Method: "*middle"},
|
||||
},
|
||||
}
|
||||
if rsrField, err := NewRSRParser(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField, rsrField) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", expRSRField, rsrField)
|
||||
}
|
||||
// One extra separator but escaped
|
||||
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)\/@/0$1/`
|
||||
expRSRField3 := &RSRParser{
|
||||
path: "~sip_redirected_to",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)\/@`),
|
||||
ReplaceTemplate: "0$1",
|
||||
}},
|
||||
}
|
||||
if rsrField, err := NewRSRParser(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField3, rsrField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expRSRField3, rsrField)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewRSRParserDDz(t *testing.T) {
|
||||
rulesStr := `~effective_caller_id_number:s/(\d+)/+$1/`
|
||||
expectRSRField := &RSRParser{
|
||||
path: "~effective_caller_id_number",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`(\d+)`),
|
||||
ReplaceTemplate: "+$1",
|
||||
}},
|
||||
}
|
||||
if rsrField, err := NewRSRParser(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRSRParserIvo(t *testing.T) {
|
||||
rulesStr := `~cost_details:s/MatchedDestId":".+_(\s\s\s\s\s)"/$1/`
|
||||
expectRSRField := &RSRParser{
|
||||
path: "~cost_details",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`MatchedDestId":".+_(\s\s\s\s\s)"`),
|
||||
ReplaceTemplate: "$1",
|
||||
}},
|
||||
}
|
||||
if rsrField, err := NewRSRParser(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
if _, err := NewRSRParser(`~account:s/^[A-Za-z0-9]*[c|a]\d{4}$/S/:s/^[A-Za-z0-9]*n\d{4}$/C/:s/^\d{10}$//`); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPlusNationalAnd00(t *testing.T) {
|
||||
rulesStr := `~effective_caller_id_number:s/\+49(\d+)/0$1/:s/\+(\d+)/00$1/`
|
||||
expectRSRField := &RSRParser{
|
||||
path: "~effective_caller_id_number",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{
|
||||
{
|
||||
SearchRegexp: regexp.MustCompile(`\+49(\d+)`),
|
||||
ReplaceTemplate: "0$1",
|
||||
},
|
||||
{
|
||||
SearchRegexp: regexp.MustCompile(`\+(\d+)`),
|
||||
ReplaceTemplate: "00$1",
|
||||
},
|
||||
},
|
||||
}
|
||||
rsrField, err := NewRSRParser(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.ParseValue("+4986517174963"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "086517174963" {
|
||||
t.Errorf("Expecting: 086517174963, received: %s", parsedVal)
|
||||
}
|
||||
if parsedVal, err := rsrField.ParseValue("+3186517174963"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "003186517174963" {
|
||||
t.Errorf("Expecting: 003186517174963, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertDurToSecs(t *testing.T) {
|
||||
rulesStr := `~9:s/^(\d+)$/${1}s/`
|
||||
expectRSRField := &RSRParser{
|
||||
path: "~9",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`^(\d+)$`),
|
||||
ReplaceTemplate: "${1}s",
|
||||
}},
|
||||
}
|
||||
rsrField, err := NewRSRParser(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.ParseValue("640113"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "640113s" {
|
||||
t.Errorf("Expecting: 640113s, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix164(t *testing.T) {
|
||||
rulesStr := `~0:s/^([1-9]\d+)$/+$1/`
|
||||
expectRSRField := &RSRParser{
|
||||
path: "~0",
|
||||
Rules: rulesStr,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`^([1-9]\d+)$`),
|
||||
ReplaceTemplate: "+$1",
|
||||
}},
|
||||
}
|
||||
rsrField, err := NewRSRParser(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.ParseValue("4986517174960"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "+4986517174960" {
|
||||
t.Errorf("Expecting: +4986517174960, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRSRParsers2(t *testing.T) {
|
||||
fieldsStr1 := `~account:s/^\w+[mpls]\d{6}$//;~subject:s/^0\d{9}$//;~mediation_runid:s/^default$/default/`
|
||||
rsrFld1, err := NewRSRParser(`~account:s/^\w+[mpls]\d{6}$//`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsrFld2, err := NewRSRParser(`~subject:s/^0\d{9}$//`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsrFld4, err := NewRSRParser(`~mediation_runid:s/^default$/default/`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eRSRFields := RSRParsers{rsrFld1, rsrFld2, rsrFld4}
|
||||
if rsrFlds, err := NewRSRParsers(fieldsStr1, utils.INFIELD_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else if !reflect.DeepEqual(eRSRFields, rsrFlds) {
|
||||
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFlds)
|
||||
}
|
||||
fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination`
|
||||
expectParsedFields := RSRParsers{
|
||||
{
|
||||
path: "host",
|
||||
Rules: "host",
|
||||
},
|
||||
{
|
||||
path: "~sip_redirected_to",
|
||||
Rules: `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/`,
|
||||
rsrRules: []*utils.ReSearchReplace{{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`),
|
||||
ReplaceTemplate: "0$1",
|
||||
}},
|
||||
},
|
||||
{
|
||||
path: "destination",
|
||||
Rules: "destination",
|
||||
},
|
||||
}
|
||||
if parsedFields, err := NewRSRParsers(fields, utils.FIELDS_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(parsedFields, expectParsedFields) {
|
||||
t.Errorf("Expected: %s ,received: %s ", utils.ToJSON(expectParsedFields), utils.ToJSON(parsedFields))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCdrcDn1(t *testing.T) {
|
||||
rl, err := NewRSRParser(`~1:s/^00(\d+)(?:[a-zA-Z].{3})*0*([1-9]\d+)$/+$1$2/:s/^\+49(18\d{2})$/+491400$1/`)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
if parsed, err := rl.ParseValue("0049ABOC0630415354"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed != "+49630415354" {
|
||||
t.Errorf("Expecting: +49630415354, received: %s", parsed)
|
||||
}
|
||||
if parsed2, err := rl.ParseValue("00491888"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed2 != "+4914001888" {
|
||||
t.Errorf("Expecting: +4914001888, received: %s", parsed2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRCostDetails(t *testing.T) {
|
||||
fieldsStr1 := `{"Category":"default_route","Tenant":"demo.cgrates.org","Subject":"voxbeam_premium","Account":"6335820713","Destination":"15143606781","ToR":"*voice","Cost":0.0007,"Timespans":[{"TimeStart":"2015-08-30T21:46:54Z","TimeEnd":"2015-08-30T21:47:06Z","Cost":0.00072,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":0,"RoundingMethod":"*middle","RoundingDecimals":5,"MaxCost":0,"MaxCostStrategy":"0","Rates":[{"GroupIntervalStart":0,"Value":0.0036,"RateIncrement":6000000000,"RateUnit":60000000000}]},"Weight":10},"DurationIndex":12000000000,"Increments":[{"Duration":6000000000,"Cost":0.00036,"BalanceInfo":{"UnitBalanceUuid":"","MoneyBalanceUuid":"40adda88-25d3-4009-b928-f39d61590439","AccountId":"*out:demo.cgrates.org:6335820713"},"BalanceRateInterval":null,"UnitInfo":null,"CompressFactor":2}],"MatchedSubject":"*out:demo.cgrates.org:default_route:voxbeam_premium","MatchedPrefix":"1514","MatchedDestId":"Canada","RatingPlanId":"RP_VOXBEAM_PREMIUM"}]}`
|
||||
rsrField, err := NewRSRParser(`~cost_details:s/"MatchedDestId":"(\w+)"/${1}/`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if parsedVal, err := rsrField.ParseValue(fieldsStr1); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "Canada" {
|
||||
t.Errorf("Expecting: Canada, received: %s", parsedVal)
|
||||
}
|
||||
fieldsStr2 := `{"Category":"call","Tenant":"sip.test.cgrates.org","Subject":"dan","Account":"dan","Destination":"+4986517174963","ToR":"*voice","Cost":0,"Timespans":[{"TimeStart":"2015-05-13T15:03:34+02:00","TimeEnd":"2015-05-13T15:03:38+02:00","Cost":0,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":0,"RoundingMethod":"*middle","RoundingDecimals":4,"MaxCost":0,"MaxCostStrategy":"","Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":1000000000,"RateUnit":60000000000}]},"Weight":10},"DurationIndex":4000000000,"Increments":[{"Duration":1000000000,"Cost":0,"BalanceInfo":{"Unit":null,"Monetary":null,"AccountID":""},"CompressFactor":4}],"RoundIncrement":null,"MatchedSubject":"*out:sip.test.cgrates.org:call:*any","MatchedPrefix":"+31800","MatchedDestId":"CST_49800_DE080","RatingPlanId":"ISC_V","CompressFactor":1}],"RatedUsage":4}`
|
||||
rsrField, err = NewRSRParser(`~CostDetails:s/"MatchedDestId":.*_(\w{5})/${1}/:s/"MatchedDestId":"INTERNAL"/ON010/`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eMatch := "DE080"
|
||||
if parsedVal, err := rsrField.ParseValue(fieldsStr2); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != eMatch {
|
||||
t.Errorf("Expecting: <%s>, received: <%s>", eMatch, parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFldParse(t *testing.T) {
|
||||
// with dataConverters
|
||||
rulesStr := `~Usage:s/(\d+)/${1}ms/{*duration_seconds&*round:1:*middle}`
|
||||
rsrField, err := NewRSRParser(rulesStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eOut := "2.2"
|
||||
if out, err := rsrField.ParseValue("2210"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
rulesStr = `~Usage:s/(\d+)/${1}ms/{*duration_seconds&*round}`
|
||||
if rsrField, err = NewRSRParser(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eOut = "2"
|
||||
if out, err := rsrField.ParseValue("2210"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
rulesStr = `~Usage{*duration_seconds}`
|
||||
rsrField, err = NewRSRParser(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eOut = "10"
|
||||
if out, err := rsrField.ParseValue("10000000000"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
import (
|
||||
"github.com/cgrates/cgrates/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := &CmdParse{
|
||||
@@ -71,9 +73,9 @@ func (self *CmdParse) LocalExecute() string {
|
||||
if self.rpcParams.Value == "" {
|
||||
return "Empty value error"
|
||||
}
|
||||
if rsrField, err := utils.NewRSRField(self.rpcParams.Expression); err != nil {
|
||||
if rsrField, err := config.NewRSRParser(self.rpcParams.Expression); err != nil {
|
||||
return err.Error()
|
||||
} else if parsed, err := rsrField.Parse(self.rpcParams.Value); err != nil {
|
||||
} else if parsed, err := rsrField.ParseValue(self.rpcParams.Value); err != nil {
|
||||
return err.Error()
|
||||
} else {
|
||||
return parsed
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"extra_fields": ["PayPalAccount", "LCRProfile", "ResourceID"],
|
||||
"extra_fields": ["~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"],
|
||||
"chargers_conns":["*localhost"],
|
||||
"store_cdrs": true,
|
||||
"online_cdr_exports": [],
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"extra_fields": ["PayPalAccount", "LCRProfile", "ResourceID"],
|
||||
"extra_fields": ["~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"],
|
||||
"chargers_conns":["conn1"],
|
||||
"store_cdrs": true,
|
||||
"online_cdr_exports": [],
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"extra_fields": ["PayPalAccount", "LCRProfile", "ResourceID"],
|
||||
"extra_fields": ["~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"],
|
||||
"chargers_conns":["*localhost"],
|
||||
"store_cdrs": true,
|
||||
"online_cdr_exports": []
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"extra_fields": ["PayPalAccount", "LCRProfile", "ResourceID"],
|
||||
"extra_fields": ["~*req.PayPalAccount", "~*req.LCRProfile", "~*req.ResourceID"],
|
||||
"chargers_conns":["conn1"],
|
||||
"store_cdrs": true,
|
||||
"online_cdr_exports": []
|
||||
|
||||
@@ -168,7 +168,7 @@ xml_root_path
|
||||
Used in case of XML content and will specify the prefix path applied to each xml element read.
|
||||
|
||||
tenant
|
||||
Will auto-populate the Tenant within the API calls sent to CGRateS. It has the form of a RSRField. If undefined, default one from *general* section will be used.
|
||||
Will auto-populate the Tenant within the API calls sent to CGRateS. It has the form of a RSRParser. If undefined, default one from *general* section will be used.
|
||||
|
||||
timezone
|
||||
Defines the timezone for source content which does not carry that information. If undefined, default one from *general* section will be used.
|
||||
|
||||
@@ -20,9 +20,7 @@ package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -78,16 +76,18 @@ func (fsCdr FSCdr) getCGRID() string {
|
||||
|
||||
func (fsCdr FSCdr) getExtraFields() map[string]string {
|
||||
extraFields := make(map[string]string, len(fsCdr.cgrCfg.CdrsCfg().ExtraFields))
|
||||
const dynprefix string = utils.MetaDynReq + utils.NestingSep
|
||||
for _, field := range fsCdr.cgrCfg.CdrsCfg().ExtraFields {
|
||||
origFieldVal, foundInVars := fsCdr.vars[field.Id]
|
||||
if strings.HasPrefix(field.Id, utils.STATIC_VALUE_PREFIX) { // Support for static values injected in the CDRS. it will show up as {^value:value}
|
||||
foundInVars = true
|
||||
if !strings.HasPrefix(field.Rules, dynprefix) {
|
||||
continue
|
||||
}
|
||||
attrName := field.AttrName()[5:]
|
||||
origFieldVal, foundInVars := fsCdr.vars[attrName]
|
||||
if !foundInVars {
|
||||
origFieldVal = fsCdr.searchExtraField(field.Id, fsCdr.body)
|
||||
origFieldVal = fsCdr.searchExtraField(attrName, fsCdr.body)
|
||||
}
|
||||
if parsed, err := field.Parse(origFieldVal); err == nil {
|
||||
extraFields[field.Id] = parsed
|
||||
if parsed, err := field.ParseValue(origFieldVal); err == nil {
|
||||
extraFields[attrName] = parsed
|
||||
}
|
||||
}
|
||||
return extraFields
|
||||
@@ -95,31 +95,22 @@ func (fsCdr FSCdr) getExtraFields() map[string]string {
|
||||
|
||||
func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (result string) {
|
||||
for key, value := range body {
|
||||
if key == field {
|
||||
return utils.IfaceAsString(value)
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
if key == field {
|
||||
return v
|
||||
}
|
||||
case float64:
|
||||
if key == field {
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if result = fsCdr.searchExtraField(field, v); result != "" {
|
||||
if result = fsCdr.searchExtraField(field, v); len(result) != 0 {
|
||||
return
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range v {
|
||||
if otherMap, ok := item.(map[string]interface{}); ok {
|
||||
if result = fsCdr.searchExtraField(field, otherMap); result != "" {
|
||||
if result = fsCdr.searchExtraField(field, otherMap); len(result) != 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
utils.Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
utils.Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v)))
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -155,8 +146,8 @@ func (fsCdr FSCdr) AsCDR(timezone string) (storCdr *CDR, err error) {
|
||||
CostSource: fsCdr.vars["cgr_costsource"],
|
||||
Cost: -1,
|
||||
}
|
||||
if orderId, hasIt := fsCdr.vars["cgr_orderid"]; hasIt {
|
||||
if storCdr.OrderID, err = strconv.ParseInt(orderId, 10, 64); err != nil {
|
||||
if orderID, hasIt := fsCdr.vars["cgr_orderid"]; hasIt {
|
||||
if storCdr.OrderID, err = strconv.ParseInt(orderID, 10, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ func TestFsCdrFirstNonEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFsCdrCDRFields(t *testing.T) {
|
||||
fsCdrCfg.CdrsCfg().ExtraFields = []*utils.RSRField{{Id: "sip_user_agent"}}
|
||||
fsCdrCfg.CdrsCfg().ExtraFields = config.NewRSRParsersMustCompile("~*req.sip_user_agent", utils.FIELDS_SEP)
|
||||
reader := bytes.NewReader(body)
|
||||
fsCdr, err := NewFSCdr(reader, fsCdrCfg)
|
||||
if err != nil {
|
||||
@@ -416,15 +416,23 @@ func TestFsCdrCDRFields(t *testing.T) {
|
||||
setupTime, _ := utils.ParseTimeDetectLayout("1515666344", "")
|
||||
answerTime, _ := utils.ParseTimeDetectLayout("1515666347", "")
|
||||
expctCDR := &CDR{
|
||||
CGRID: "24b5766be325fa751fab5a0a06373e106f33a257",
|
||||
ToR: utils.VOICE, OriginID: "3da8bf84-c133-4959-9e24-e72875cb33a1",
|
||||
OriginHost: "", Source: "freeswitch_json", Category: "call",
|
||||
RequestType: utils.META_RATED, Tenant: "cgrates.org",
|
||||
Account: "1001", Subject: "1001",
|
||||
Destination: "1002", SetupTime: setupTime,
|
||||
AnswerTime: answerTime, Usage: 68 * time.Second,
|
||||
CGRID: "24b5766be325fa751fab5a0a06373e106f33a257",
|
||||
ToR: utils.VOICE,
|
||||
OriginID: "3da8bf84-c133-4959-9e24-e72875cb33a1",
|
||||
OriginHost: "",
|
||||
Source: "freeswitch_json",
|
||||
Category: "call",
|
||||
RequestType: utils.META_RATED,
|
||||
Tenant: "cgrates.org",
|
||||
Account: "1001",
|
||||
Subject: "1001",
|
||||
Destination: "1002",
|
||||
SetupTime: setupTime,
|
||||
AnswerTime: answerTime,
|
||||
Usage: 68 * time.Second,
|
||||
Cost: -1,
|
||||
ExtraFields: map[string]string{"sip_user_agent": "Jitsi2.10.5550Linux"}}
|
||||
ExtraFields: map[string]string{"sip_user_agent": "Jitsi2.10.5550Linux"},
|
||||
}
|
||||
if CDR, err := fsCdr.AsCDR(""); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(expctCDR, CDR) {
|
||||
@@ -450,14 +458,13 @@ func TestFsCdrSearchExtraField(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
rsrSt1, _ := utils.NewRSRField("^injected_value")
|
||||
rsrSt2, _ := utils.NewRSRField("^injected_hdr::injected_value/")
|
||||
fsCdrCfg.CdrsCfg().ExtraFields = []*utils.RSRField{{Id: "caller_id_name"}, rsrSt1, rsrSt2}
|
||||
fsCdrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"~*req.caller_id_name"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
extraFields := fsCdr.getExtraFields()
|
||||
if len(extraFields) != 3 || extraFields["caller_id_name"] != "1001" ||
|
||||
extraFields["injected_value"] != "injected_value" ||
|
||||
extraFields["injected_hdr"] != "injected_value" {
|
||||
t.Error("Error parsing extra fields: ", extraFields)
|
||||
if len(extraFields) != 1 || extraFields["caller_id_name"] != "1001" {
|
||||
t.Error("Error parsing extra fields: ", utils.ToJSON(extraFields))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -472,7 +479,7 @@ func TestFsCdrSearchExtraFieldInSlice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFsCdrSearchReplaceInExtraFields(t *testing.T) {
|
||||
fsCdrCfg.CdrsCfg().ExtraFields = utils.ParseRSRFieldsMustCompile(`read_codec;~sip_user_agent:s/([A-Za-z]*).+/$1/;write_codec`, utils.INFIELD_SEP)
|
||||
fsCdrCfg.CdrsCfg().ExtraFields = config.NewRSRParsersMustCompile(`~*req.read_codec;~*req.sip_user_agent:s/([A-Za-z]*).+/$1/;~*req.write_codec`, utils.INFIELD_SEP)
|
||||
newReader := bytes.NewReader(body)
|
||||
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
|
||||
if err != nil {
|
||||
@@ -483,15 +490,15 @@ func TestFsCdrSearchReplaceInExtraFields(t *testing.T) {
|
||||
t.Error("Error parsing extra fields: ", extraFields)
|
||||
}
|
||||
if extraFields["sip_user_agent"] != "Jitsi" {
|
||||
t.Error("Error parsing extra fields: ", extraFields)
|
||||
t.Error("Error parsing extra fields: ", utils.ToJSON(extraFields))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFsCdrDDazRSRExtraFields(t *testing.T) {
|
||||
eFieldsCfg := `{"cdrs": {
|
||||
"extra_fields": ["~effective_caller_id_number:s/(\\d+)/+$1/"],
|
||||
"extra_fields": ["~*req.effective_caller_id_number:s/(\\d+)/+$1/"],
|
||||
},}`
|
||||
simpleJsonCdr := []byte(`{
|
||||
simpleJSONCdr := []byte(`{
|
||||
"core-uuid": "feef0b51-7fdf-4c4a-878e-aff233752de2",
|
||||
"channel_data": {
|
||||
"state": "CS_REPORTING",
|
||||
@@ -522,13 +529,13 @@ func TestFsCdrDDazRSRExtraFields(t *testing.T) {
|
||||
}`)
|
||||
var err error
|
||||
fsCdrCfg, err = config.NewCGRConfigFromJSONStringWithDefaults(eFieldsCfg)
|
||||
expCdrExtra := utils.ParseRSRFieldsMustCompile(`~effective_caller_id_number:s/(\d+)/+$1/`, utils.INFIELD_SEP)
|
||||
expCdrExtra := config.NewRSRParsersMustCompile(`~*req.effective_caller_id_number:s/(\d+)/+$1/`, utils.INFIELD_SEP)
|
||||
if err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(expCdrExtra[0], fsCdrCfg.CdrsCfg().ExtraFields[0]) { // Kinda deepEqual bug since without index does not match
|
||||
t.Errorf("Expecting: %+v, received: %+v", expCdrExtra, fsCdrCfg.CdrsCfg().ExtraFields)
|
||||
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expCdrExtra), utils.ToJSON(fsCdrCfg.CdrsCfg().ExtraFields))
|
||||
}
|
||||
newReader := bytes.NewReader(simpleJsonCdr)
|
||||
newReader := bytes.NewReader(simpleJSONCdr)
|
||||
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
|
||||
if err != nil {
|
||||
t.Error("Could not parse cdr", err.Error())
|
||||
@@ -557,7 +564,7 @@ func TestFscdrAsCDR(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cgrCfg.CdrsCfg().ExtraFields, err = utils.ParseRSRFieldsFromSlice([]string{"PayPalAccount"})
|
||||
cgrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"~*req.PayPalAccount"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -793,16 +800,14 @@ func TestGetExtraFields(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
cgrCfg.CdrsCfg().ExtraFields, err = utils.ParseRSRFieldsFromSlice([]string{"^^PayPalAccount"})
|
||||
cgrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"PayPalAccount"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fsCdr := FSCdr{
|
||||
cgrCfg: cgrCfg,
|
||||
}
|
||||
expected := map[string]string{
|
||||
"^PayPalAccount": "^PayPalAccount",
|
||||
}
|
||||
expected := map[string]string{}
|
||||
if reply := fsCdr.getExtraFields(); !reflect.DeepEqual(reply, expected) {
|
||||
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expected), utils.ToJSON(reply))
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ cgrates (0.11.0~dev) UNRELEASED; urgency=medium
|
||||
* [ConfigS] Renamed ReloadConfigFromPath API to ReloadConfig
|
||||
* [ConfigS] Renamed ReloadConfig API to SetConfig
|
||||
* [ConfigS] Renamed ReloadConfigFromJSON API to SetConfigFromJSON
|
||||
* [CDRs] Replaced RSRField with RSRParser
|
||||
|
||||
-- DanB <danb@cgrates.org> Wed, 19 Feb 2020 13:25:52 +0200
|
||||
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
spltRgxp = regexp.MustCompile(`:s\/`)
|
||||
rulesRgxp = regexp.MustCompile(`(?:(.+[^\\])\/(.*[^\\])*\/){1,}`)
|
||||
)
|
||||
|
||||
func NewRSRField(fldStr string) (fld *RSRField, err error) {
|
||||
if len(fldStr) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
rsrField := &RSRField{Rules: fldStr}
|
||||
var filters []*RSRFilter
|
||||
if strings.HasSuffix(fldStr, FilterValEnd) { // Has filter, populate the var
|
||||
fltrStart := strings.LastIndex(fldStr, FilterValStart)
|
||||
if fltrStart < 1 {
|
||||
return nil, fmt.Errorf("Invalid FilterStartValue in string: %s", fldStr)
|
||||
}
|
||||
fltrVal := fldStr[fltrStart+1 : len(fldStr)-1]
|
||||
filters, err = ParseRSRFilters(fltrVal, MetaPipe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid FilterValue in string: %s, err: %s", fltrVal, err.Error())
|
||||
}
|
||||
fldStr = fldStr[:fltrStart] // Take the filter part out before compiling further
|
||||
|
||||
}
|
||||
if idxConverters := strings.Index(fldStr, "{*"); idxConverters != -1 { // converters in the string
|
||||
if !strings.HasSuffix(fldStr, "}") {
|
||||
return nil,
|
||||
fmt.Errorf("Invalid converter value in string: %s, err: invalid converter terminator",
|
||||
fldStr)
|
||||
}
|
||||
convertersStr := fldStr[idxConverters+1 : len(fldStr)-1] // strip also {}
|
||||
convsSplt := strings.Split(convertersStr, INFIELD_SEP)
|
||||
rsrField.converters = make([]DataConverter, 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 {
|
||||
rsrField.converters[i] = conv
|
||||
}
|
||||
}
|
||||
fldStr = fldStr[:idxConverters]
|
||||
}
|
||||
if strings.HasPrefix(fldStr, STATIC_VALUE_PREFIX) { // Special case when RSR is defined as static header/value
|
||||
var staticHdr, staticVal string
|
||||
if splt := strings.Split(fldStr, STATIC_HDRVAL_SEP); len(splt) == 2 { // Using / as separator since ':' is often use in date/time fields
|
||||
staticHdr, staticVal = splt[0][1:], splt[1] // Strip the / suffix
|
||||
if strings.HasSuffix(staticVal, "/") { // If value ends with /, 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 string: %s", fldStr)
|
||||
} else {
|
||||
staticHdr, staticVal = splt[0][1:], splt[0][1:] // If no split, header will remain as original, value as header without the prefix
|
||||
}
|
||||
rsrField.Id = staticHdr
|
||||
rsrField.staticValue = staticVal
|
||||
rsrField.filters = filters
|
||||
return rsrField, nil
|
||||
}
|
||||
if !strings.HasPrefix(fldStr, DynamicDataPrefix) {
|
||||
rsrField.Id = fldStr
|
||||
rsrField.filters = filters
|
||||
return rsrField, nil
|
||||
}
|
||||
spltRules := spltRgxp.Split(fldStr, -1)
|
||||
if len(spltRules) < 2 {
|
||||
return nil, fmt.Errorf("Invalid Split of Search&Replace field rule. %s", fldStr)
|
||||
}
|
||||
rsrField.Id = spltRules[0][1:]
|
||||
rsrField.filters = filters // Original id in form ~hdr_name
|
||||
for _, ruleStr := range spltRules[1:] { // :s/ already removed through split
|
||||
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 {
|
||||
rsrField.RSRules = append(rsrField.RSRules, &ReSearchReplace{SearchRegexp: srRegexp, ReplaceTemplate: allMatches[2]})
|
||||
}
|
||||
}
|
||||
return rsrField, nil
|
||||
}
|
||||
|
||||
func NewRSRFieldMustCompile(fldStr string) (rsrFld *RSRField) {
|
||||
var err error
|
||||
if rsrFld, err = NewRSRField(fldStr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type RSRField struct {
|
||||
Id string // Identifier
|
||||
Rules string // Rules container holding the string rules to be able to restore it after DB
|
||||
staticValue string // If defined, enforces parsing always to this value
|
||||
RSRules []*ReSearchReplace // Rules to use when processing field value
|
||||
filters []*RSRFilter // The value to compare when used as filter
|
||||
converters DataConverters // set of converters to apply on output
|
||||
}
|
||||
|
||||
// IsCompiled finds out whether this RSRField was already parsed or RAW state
|
||||
func (rsrf *RSRField) IsCompiled() bool {
|
||||
return rsrf.staticValue != "" ||
|
||||
rsrf.RSRules != nil ||
|
||||
rsrf.filters != nil ||
|
||||
rsrf.converters != nil
|
||||
}
|
||||
|
||||
// Compile parses Rules string and repopulates other fields
|
||||
func (rsrf *RSRField) Compile() error {
|
||||
if newRSRFld, err := NewRSRField(rsrf.Rules); err != nil {
|
||||
return err
|
||||
} else if newRSRFld != nil {
|
||||
rsrf.staticValue = newRSRFld.staticValue
|
||||
rsrf.RSRules = newRSRFld.RSRules
|
||||
rsrf.filters = newRSRFld.filters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseValue the field value from a string
|
||||
func (rsrf *RSRField) parseValue(value string) string {
|
||||
if len(rsrf.staticValue) != 0 { // Enforce parsing of static values
|
||||
return rsrf.staticValue
|
||||
}
|
||||
for _, rsRule := range rsrf.RSRules {
|
||||
if rsRule != nil {
|
||||
value = rsRule.Process(value)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (rsrf *RSRField) filtersPassing(value string) bool {
|
||||
for _, fltr := range rsrf.filters {
|
||||
if !fltr.Pass(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Parse will parse the value out considering converters and filters
|
||||
func (rsrf *RSRField) Parse(value interface{}) (out string, err error) {
|
||||
out = rsrf.parseValue(IfaceAsString(value))
|
||||
if out, err = rsrf.converters.ConvertString(out); err != nil {
|
||||
return
|
||||
}
|
||||
if !rsrf.filtersPassing(out) {
|
||||
return "", ErrFilterNotPassingNoCaps
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewRSRFilter instantiates a new RSRFilter, setting it's properties
|
||||
func NewRSRFilter(fltrVal string) (rsrFltr *RSRFilter, err error) {
|
||||
rsrFltr = new(RSRFilter)
|
||||
if fltrVal == "" {
|
||||
return rsrFltr, nil
|
||||
}
|
||||
if fltrVal[:1] == NegativePrefix {
|
||||
rsrFltr.negative = true
|
||||
fltrVal = fltrVal[1:]
|
||||
if fltrVal == "" {
|
||||
return rsrFltr, nil
|
||||
}
|
||||
}
|
||||
rsrFltr.filterRule = fltrVal
|
||||
if fltrVal[:1] == DynamicDataPrefix {
|
||||
if rsrFltr.fltrRgxp, err = regexp.Compile(fltrVal[1:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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
|
||||
fltrRgxp *regexp.Regexp
|
||||
negative bool // Rule should not match
|
||||
}
|
||||
|
||||
func (rsrFltr *RSRFilter) FilterRule() string {
|
||||
return rsrFltr.filterRule
|
||||
}
|
||||
|
||||
func (rsrFltr *RSRFilter) Pass(val string) bool {
|
||||
if rsrFltr.filterRule == "" {
|
||||
return !rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule[:1] == DynamicDataPrefix {
|
||||
return rsrFltr.fltrRgxp.MatchString(val) != rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule == "^$" { // Special case to test empty value
|
||||
return len(val) == 0 != rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule[:1] == MatchStartPrefix {
|
||||
if rsrFltr.filterRule[len(rsrFltr.filterRule)-1:] == MatchEndPrefix { // starts with ^ and ends with $, exact match
|
||||
return val == rsrFltr.filterRule[1:len(rsrFltr.filterRule)-1] != rsrFltr.negative
|
||||
}
|
||||
return strings.HasPrefix(val, rsrFltr.filterRule[1:]) != rsrFltr.negative
|
||||
}
|
||||
lastIdx := len(rsrFltr.filterRule) - 1
|
||||
if rsrFltr.filterRule[lastIdx:] == MatchEndPrefix {
|
||||
return strings.HasSuffix(val, rsrFltr.filterRule[:lastIdx]) != rsrFltr.negative
|
||||
}
|
||||
if len(rsrFltr.filterRule) > 2 && rsrFltr.filterRule[:2] == MatchGreaterThanOrEqual {
|
||||
gt, err := GreaterThan(StringToInterface(val),
|
||||
StringToInterface(rsrFltr.filterRule[2:]), true)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if len(rsrFltr.filterRule) > 2 && rsrFltr.filterRule[:2] == MatchLessThanOrEqual {
|
||||
gt, err := GreaterThan(StringToInterface(rsrFltr.filterRule[2:]), // compare the rule with the val
|
||||
StringToInterface(val),
|
||||
true)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if rsrFltr.filterRule[:1] == MatchGreaterThan {
|
||||
gt, err := GreaterThan(StringToInterface(val),
|
||||
StringToInterface(rsrFltr.filterRule[1:]), false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if rsrFltr.filterRule[:1] == MatchLessThan {
|
||||
gt, err := GreaterThan(StringToInterface(rsrFltr.filterRule[1:]), // compare the rule with the val
|
||||
StringToInterface(val),
|
||||
false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
return (strings.Index(val, rsrFltr.filterRule) != -1) != rsrFltr.negative // default is string index
|
||||
}
|
||||
|
||||
func ParseRSRFilters(fldsStr, sep string) (RSRFilters, error) {
|
||||
if fldsStr == "" {
|
||||
return nil, nil
|
||||
}
|
||||
fltrSplt := strings.Split(fldsStr, sep)
|
||||
return ParseRSRFiltersFromSlice(fltrSplt)
|
||||
}
|
||||
|
||||
func ParseRSRFiltersFromSlice(fltrStrs []string) (RSRFilters, error) {
|
||||
rsrFltrs := make(RSRFilters, len(fltrStrs))
|
||||
for i, rlStr := range fltrStrs {
|
||||
if rsrFltr, err := NewRSRFilter(rlStr); err != nil {
|
||||
return nil, err
|
||||
} else if rsrFltr == nil {
|
||||
return nil, fmt.Errorf("Empty RSRFilter in rule: %s", rlStr)
|
||||
} else {
|
||||
rsrFltrs[i] = rsrFltr
|
||||
}
|
||||
}
|
||||
return rsrFltrs, nil
|
||||
}
|
||||
|
||||
type RSRFilters []*RSRFilter
|
||||
|
||||
func (fltrs RSRFilters) FilterRules() (rls string) {
|
||||
for _, fltr := range fltrs {
|
||||
rls += fltr.FilterRule()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// @all: specifies whether all filters should match or at least one
|
||||
func (fltrs RSRFilters) Pass(val string, allMustMatch bool) (matched bool) {
|
||||
if len(fltrs) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, fltr := range fltrs {
|
||||
matched = fltr.Pass(val)
|
||||
if allMustMatch {
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
} else if matched {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ParseRSRFieldsFromSlice(flds []string) (RSRFields, error) {
|
||||
if len(flds) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
rsrFields := make(RSRFields, len(flds))
|
||||
for idx, ruleStr := range flds {
|
||||
if rsrField, err := NewRSRField(ruleStr); err != nil {
|
||||
return nil, err
|
||||
} else if rsrField == nil {
|
||||
return nil, fmt.Errorf("Empty RSRField in rule: %s", ruleStr)
|
||||
} else {
|
||||
rsrFields[idx] = rsrField
|
||||
}
|
||||
}
|
||||
return rsrFields, nil
|
||||
|
||||
}
|
||||
|
||||
// Parses list of RSRFields, used for example as multiple filters in derived charging
|
||||
func ParseRSRFields(fldsStr, sep string) (RSRFields, error) {
|
||||
//rsrRlsPattern := regexp.MustCompile(`^(~\w+:s/.+/.*/)|(\^.+(/.+/)?)(;(~\w+:s/.+/.*/)|(\^.+(/.+/)?))*$`) //ToDo:Fix here rule able to confirm the content
|
||||
if len(fldsStr) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
rulesSplt := strings.Split(fldsStr, sep)
|
||||
return ParseRSRFieldsFromSlice(rulesSplt)
|
||||
|
||||
}
|
||||
|
||||
func ParseRSRFieldsMustCompile(fldsStr, sep string) RSRFields {
|
||||
if flds, err := ParseRSRFields(fldsStr, sep); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return flds
|
||||
}
|
||||
}
|
||||
|
||||
type RSRFields []*RSRField
|
||||
|
||||
// Return first Id of the rsrFields, used in cdre
|
||||
func (flds RSRFields) Id() string {
|
||||
if len(flds) == 0 {
|
||||
return ""
|
||||
}
|
||||
return flds[0].Id
|
||||
}
|
||||
|
||||
func (flds RSRFields) Compile() (err error) {
|
||||
for _, rsrFld := range flds {
|
||||
if err = rsrFld.Compile(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,730 +0,0 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewRSRField(t *testing.T) {
|
||||
// Empty case
|
||||
if rcv, err := NewRSRField(EmptyString); err != nil {
|
||||
t.Error(err)
|
||||
} else if rcv != nil {
|
||||
t.Error("Expecting nill, received: ", rcv)
|
||||
}
|
||||
// Normal case
|
||||
rulesStr := `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/(someval)`
|
||||
filter, _ := NewRSRFilter("someval")
|
||||
expRSRField1 := &RSRField{
|
||||
Id: "sip_redirected_to",
|
||||
Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{
|
||||
{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`),
|
||||
ReplaceTemplate: "0$1"}},
|
||||
filters: []*RSRFilter{filter},
|
||||
converters: nil}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField1, rsrField) {
|
||||
t.Errorf("Expecting: %+v, received: %+v",
|
||||
expRSRField1, rsrField)
|
||||
}
|
||||
// With filter
|
||||
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/(086517174963)`
|
||||
// rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/{*duration_seconds;*round:5:*middle}(086517174963)`
|
||||
filter, _ = NewRSRFilter("086517174963")
|
||||
expRSRField2 := &RSRField{Id: "sip_redirected_to", Rules: rulesStr, filters: []*RSRFilter{filter},
|
||||
RSRules: []*ReSearchReplace{{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`), ReplaceTemplate: "0$1"}}}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField2, rsrField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expRSRField2, rsrField)
|
||||
}
|
||||
// with dataConverters
|
||||
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/{*duration_seconds;*round:5:*middle}(086517174963)`
|
||||
filter, _ = NewRSRFilter("086517174963")
|
||||
expRSRField := &RSRField{
|
||||
Id: "sip_redirected_to",
|
||||
Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{
|
||||
{
|
||||
SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`),
|
||||
ReplaceTemplate: "0$1"}},
|
||||
filters: []*RSRFilter{filter},
|
||||
converters: []DataConverter{
|
||||
new(DurationSecondsConverter), &RoundConverter{Decimals: 5, Method: "*middle"}}}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField, rsrField) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", expRSRField, rsrField)
|
||||
}
|
||||
// One extra separator but escaped
|
||||
rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)\/@/0$1/`
|
||||
expRSRField3 := &RSRField{Id: "sip_redirected_to", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)\/@`), ReplaceTemplate: "0$1"}}}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expRSRField3, rsrField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expRSRField3, rsrField)
|
||||
}
|
||||
if _, err := NewRSRField("(test)"); err == nil || err.Error() != "Invalid FilterStartValue in string: (test)" {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := NewRSRField("{*test"); err == nil || err.Error() != "Invalid converter value in string: {*test, err: invalid converter terminator" {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := NewRSRField("^t::e::s::t::"); err == nil || err.Error() != "Invalid RSRField string: ^t::e::s::t::" {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := NewRSRField("~-1"); err == nil || err.Error() != "Invalid Split of Search&Replace field rule. ~-1" {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRSRFieldDDz(t *testing.T) {
|
||||
rulesStr := `~effective_caller_id_number:s/(\d+)/+$1/`
|
||||
expectRSRField := &RSRField{Id: "effective_caller_id_number", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{{SearchRegexp: regexp.MustCompile(`(\d+)`), ReplaceTemplate: "+$1"}}}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRSRFieldIvo(t *testing.T) {
|
||||
rulesStr := `~cost_details:s/MatchedDestId":".+_(\s\s\s\s\s)"/$1/`
|
||||
expectRSRField := &RSRField{Id: "cost_details", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{{SearchRegexp: regexp.MustCompile(`MatchedDestId":".+_(\s\s\s\s\s)"`), ReplaceTemplate: "$1"}}}
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
if _, err := NewRSRField(`~account:s/^[A-Za-z0-9]*[c|a]\d{4}$/S/:s/^[A-Za-z0-9]*n\d{4}$/C/:s/^\d{10}$//`); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPlusNationalAnd00(t *testing.T) {
|
||||
rulesStr := `~effective_caller_id_number:s/\+49(\d+)/0$1/:s/\+(\d+)/00$1/`
|
||||
expectRSRField := &RSRField{Id: "effective_caller_id_number", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{
|
||||
{SearchRegexp: regexp.MustCompile(`\+49(\d+)`), ReplaceTemplate: "0$1"},
|
||||
{SearchRegexp: regexp.MustCompile(`\+(\d+)`), ReplaceTemplate: "00$1"}}}
|
||||
rsrField, err := NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.Parse("+4986517174963"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "086517174963" {
|
||||
t.Errorf("Expecting: 086517174963, received: %s", parsedVal)
|
||||
}
|
||||
if parsedVal, err := rsrField.Parse("+3186517174963"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "003186517174963" {
|
||||
t.Errorf("Expecting: 003186517174963, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRParseStatic(t *testing.T) {
|
||||
rulesStr := "^static_header::static_value/"
|
||||
rsrField, err := NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, &RSRField{Id: "static_header", Rules: rulesStr,
|
||||
staticValue: "static_value"}) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
if parsed, err := rsrField.Parse("dynamic_value"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed != "static_value" {
|
||||
t.Errorf("Expected: %s, received: %s", "static_value", parsed)
|
||||
}
|
||||
rulesStr = `^static_hdrvalue`
|
||||
rsrField, err = NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, &RSRField{Id: "static_hdrvalue", Rules: rulesStr,
|
||||
staticValue: "static_hdrvalue"}) {
|
||||
t.Errorf("Unexpected RSRField received: %v", rsrField)
|
||||
}
|
||||
if parsed, err := rsrField.Parse("dynamic_value"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed != "static_hdrvalue" {
|
||||
t.Errorf("Expected: %s, received: %s", "static_hdrvalue", parsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertDurToSecs(t *testing.T) {
|
||||
rulesStr := `~9:s/^(\d+)$/${1}s/`
|
||||
expectRSRField := &RSRField{Id: "9", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{
|
||||
{SearchRegexp: regexp.MustCompile(`^(\d+)$`), ReplaceTemplate: "${1}s"}}}
|
||||
rsrField, err := NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.Parse("640113"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "640113s" {
|
||||
t.Errorf("Expecting: 640113s, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix164(t *testing.T) {
|
||||
rulesStr := `~0:s/^([1-9]\d+)$/+$1/`
|
||||
expectRSRField := &RSRField{Id: "0", Rules: rulesStr,
|
||||
RSRules: []*ReSearchReplace{
|
||||
{SearchRegexp: regexp.MustCompile(`^([1-9]\d+)$`), ReplaceTemplate: "+$1"}}}
|
||||
rsrField, err := NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(rsrField, expectRSRField) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectRSRField, rsrField)
|
||||
}
|
||||
if parsedVal, err := rsrField.Parse("4986517174960"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "+4986517174960" {
|
||||
t.Errorf("Expecting: +4986517174960, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSRFields(t *testing.T) {
|
||||
fieldsStr1 := `~account:s/^\w+[mpls]\d{6}$//;~subject:s/^0\d{9}$//;^destination/+4912345/;~mediation_runid:s/^default$/default/`
|
||||
rsrFld1, _ := NewRSRField(`~account:s/^\w+[mpls]\d{6}$//`)
|
||||
rsrFld2, _ := NewRSRField(`~subject:s/^0\d{9}$//`)
|
||||
rsrFld3, _ := NewRSRField(`^destination/+4912345/`)
|
||||
rsrFld4, _ := NewRSRField(`~mediation_runid:s/^default$/default/`)
|
||||
eRSRFields := RSRFields{rsrFld1, rsrFld2, rsrFld3, rsrFld4}
|
||||
if rsrFlds, err := ParseRSRFields(fieldsStr1, INFIELD_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else if !reflect.DeepEqual(eRSRFields, rsrFlds) {
|
||||
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFlds)
|
||||
}
|
||||
fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination`
|
||||
expectParsedFields := RSRFields{
|
||||
&RSRField{Id: "host", Rules: "host"},
|
||||
&RSRField{Id: "sip_redirected_to", Rules: `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/`,
|
||||
RSRules: []*ReSearchReplace{{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`), ReplaceTemplate: "0$1"}}},
|
||||
&RSRField{Id: "destination", Rules: "destination"}}
|
||||
if parsedFields, err := ParseRSRFields(fields, FIELDS_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(parsedFields, expectParsedFields) {
|
||||
t.Errorf("Unexpected value of parsed fields")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCdrcDn1(t *testing.T) {
|
||||
rl, err := NewRSRField(`~1:s/^00(\d+)(?:[a-zA-Z].{3})*0*([1-9]\d+)$/+$1$2/:s/^\+49(18\d{2})$/+491400$1/`)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
if parsed, err := rl.Parse("0049ABOC0630415354"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed != "+49630415354" {
|
||||
t.Errorf("Expecting: +49630415354, received: %s", parsed)
|
||||
}
|
||||
if parsed2, err := rl.Parse("00491888"); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsed2 != "+4914001888" {
|
||||
t.Errorf("Expecting: +4914001888, received: %s", parsed2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPasses(t *testing.T) {
|
||||
rl, err := NewRSRField(`~1:s/^00(\d+)(?:[a-zA-Z].{3})*0*([1-9]\d+)$/+$1$2/:s/^\+49(18\d{2})$/+491400$1/(+49630415354)`)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
if _, err = rl.Parse("0031ABOC0630415354"); err == nil ||
|
||||
err != ErrFilterNotPassingNoCaps {
|
||||
t.Error("Passing filter")
|
||||
}
|
||||
rl, err = NewRSRField(`~1:s/^$/_empty_/(_empty_)`)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
if _, err = rl.Parse(""); err == ErrFilterNotPassingNoCaps {
|
||||
t.Error("Not passing filter")
|
||||
}
|
||||
if _, err = rl.Parse("Non empty"); err == nil ||
|
||||
err != ErrFilterNotPassingNoCaps {
|
||||
t.Error("Passing filter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFieldsId(t *testing.T) {
|
||||
fieldsStr1 := `~account:s/^\w+[mpls]\d{6}$//;~subject:s/^0\d{9}$//;^destination/+4912345/;~mediation_runid:s/^default$/default/`
|
||||
if rsrFlds, err := ParseRSRFields(fieldsStr1, INFIELD_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else if idRcv := rsrFlds.Id(); idRcv != "account" {
|
||||
t.Errorf("Received id: %s", idRcv)
|
||||
}
|
||||
fieldsStr2 := ""
|
||||
if rsrFlds, err := ParseRSRFields(fieldsStr2, INFIELD_SEP); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else if idRcv := rsrFlds.Id(); idRcv != "" {
|
||||
t.Errorf("Received id: %s", idRcv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRCostDetails(t *testing.T) {
|
||||
fieldsStr1 := `{"Category":"default_route","Tenant":"demo.cgrates.org","Subject":"voxbeam_premium","Account":"6335820713","Destination":"15143606781","ToR":"*voice","Cost":0.0007,"Timespans":[{"TimeStart":"2015-08-30T21:46:54Z","TimeEnd":"2015-08-30T21:47:06Z","Cost":0.00072,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":0,"RoundingMethod":"*middle","RoundingDecimals":5,"MaxCost":0,"MaxCostStrategy":"0","Rates":[{"GroupIntervalStart":0,"Value":0.0036,"RateIncrement":6000000000,"RateUnit":60000000000}]},"Weight":10},"DurationIndex":12000000000,"Increments":[{"Duration":6000000000,"Cost":0.00036,"BalanceInfo":{"UnitBalanceUuid":"","MoneyBalanceUuid":"40adda88-25d3-4009-b928-f39d61590439","AccountId":"*out:demo.cgrates.org:6335820713"},"BalanceRateInterval":null,"UnitInfo":null,"CompressFactor":2}],"MatchedSubject":"*out:demo.cgrates.org:default_route:voxbeam_premium","MatchedPrefix":"1514","MatchedDestId":"Canada","RatingPlanId":"RP_VOXBEAM_PREMIUM"}]}`
|
||||
rsrField, err := NewRSRField(`~cost_details:s/"MatchedDestId":"(\w+)"/${1}/`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if parsedVal, err := rsrField.Parse(fieldsStr1); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != "Canada" {
|
||||
t.Errorf("Expecting: Canada, received: %s", parsedVal)
|
||||
}
|
||||
fieldsStr2 := `{"Category":"call","Tenant":"sip.test.cgrates.org","Subject":"dan","Account":"dan","Destination":"+4986517174963","ToR":"*voice","Cost":0,"Timespans":[{"TimeStart":"2015-05-13T15:03:34+02:00","TimeEnd":"2015-05-13T15:03:38+02:00","Cost":0,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":0,"RoundingMethod":"*middle","RoundingDecimals":4,"MaxCost":0,"MaxCostStrategy":"","Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":1000000000,"RateUnit":60000000000}]},"Weight":10},"DurationIndex":4000000000,"Increments":[{"Duration":1000000000,"Cost":0,"BalanceInfo":{"Unit":null,"Monetary":null,"AccountID":""},"CompressFactor":4}],"RoundIncrement":null,"MatchedSubject":"*out:sip.test.cgrates.org:call:*any","MatchedPrefix":"+31800","MatchedDestId":"CST_49800_DE080","RatingPlanId":"ISC_V","CompressFactor":1}],"RatedUsage":4}`
|
||||
rsrField, err = NewRSRField(`~CostDetails:s/"MatchedDestId":.*_(\w{5})/${1}/:s/"MatchedDestId":"INTERNAL"/ON010/`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eMatch := "DE080"
|
||||
if parsedVal, err := rsrField.Parse(fieldsStr2); err != nil {
|
||||
t.Error(err)
|
||||
} else if parsedVal != eMatch {
|
||||
t.Errorf("Expecting: <%s>, received: <%s>", eMatch, parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFilterPass(t *testing.T) {
|
||||
fltr, err := NewRSRFilter("") // Pass any
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("any") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!") // Pass nothing
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("any") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^full_match$") // Full string pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("full_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("full_match1") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^prefixMatch") // Prefix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("prefixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("prefixMatch12345") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("1prefixMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("suffixMatch$") // Suffix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("suffixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("12345suffixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("suffixMatch1") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!fullMatch") // Negative full pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("ShouldMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("fullMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!^prefixMatch") // Negative prefix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("prefixMatch123") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("123prefixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!suffixMatch$") // Negative suffix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("123suffixMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("suffixMatch123") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("~^C.+S$") // Regexp pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("CGRateS") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("1CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!~^C.*S$") // Negative regexp pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^$") // Empty value
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!^$") // Non-empty value
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("CGRateS") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("indexed_match") // Indexed match
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("indexed_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("suf_indexed_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("indexed_match_pref") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("suf_indexed_match_pref") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("indexed_matc") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!indexed_match") // Negative indexed match
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("indexed_match") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("suf_indexed_match") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("indexed_match_pref") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("suf_indexed_match_pref") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("indexed_matc") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
|
||||
// compare greaterThan
|
||||
fltr, err = NewRSRFilter(">0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not greaterThan
|
||||
fltr, err = NewRSRFilter("!>0s") // !(n>0s)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare greaterThanOrEqual
|
||||
fltr, err = NewRSRFilter(">=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("-1s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not greaterThanOrEqual
|
||||
fltr, err = NewRSRFilter("!>=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("-1s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare lessThan
|
||||
fltr, err = NewRSRFilter("<0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("1ns") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("-12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not lessThan
|
||||
fltr, err = NewRSRFilter("!<0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("1ns") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("-12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare lessThanOrEqual
|
||||
fltr, err = NewRSRFilter("<=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("-1s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare not lessThanOrEqual
|
||||
fltr, err = NewRSRFilter("!<=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("-1s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFiltersPass(t *testing.T) {
|
||||
rlStr := "~^C.+S$;CGRateS;ateS$"
|
||||
fltrs, err := ParseRSRFilters(rlStr, INFIELD_SEP)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltrs.Pass("CGRateS", true) {
|
||||
t.Error("Not passing")
|
||||
}
|
||||
if fltrs.Pass("ateS", true) {
|
||||
t.Error("Passing")
|
||||
}
|
||||
if !fltrs.Pass("ateS", false) {
|
||||
t.Error("Not passing")
|
||||
}
|
||||
if fltrs.Pass("teS", false) {
|
||||
t.Error("Passing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDifferentMethods(t *testing.T) {
|
||||
rlStr := `~effective_caller_id_number:s/(\d+)/+$1/`
|
||||
resParseStr, _ := ParseRSRFields(rlStr, INFIELD_SEP)
|
||||
resParseSlc, _ := ParseRSRFieldsFromSlice([]string{rlStr})
|
||||
if !reflect.DeepEqual(resParseStr, resParseSlc) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", resParseStr, resParseSlc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsParsed(t *testing.T) {
|
||||
rulesStr := `^static_hdrvalue`
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !rsrField.IsCompiled() {
|
||||
t.Error("Not compiled")
|
||||
}
|
||||
rulesStr = `~effective_caller_id_number:s/(\d+)/+$1/`
|
||||
if rsrField, err := NewRSRField(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
} else if !rsrField.IsCompiled() {
|
||||
t.Error("Not compiled")
|
||||
}
|
||||
rsrField := &RSRField{Rules: rulesStr}
|
||||
if rsrField.IsCompiled() {
|
||||
t.Error("Is compiled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileRules(t *testing.T) {
|
||||
rulesStr := `^static_hdrvalue`
|
||||
rsrField := &RSRField{Rules: rulesStr}
|
||||
if err := rsrField.Compile(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
newRSRFld, _ := NewRSRField(rulesStr)
|
||||
if reflect.DeepEqual(rsrField, newRSRFld) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", rsrField, newRSRFld)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFldParse(t *testing.T) {
|
||||
// with dataConverters
|
||||
rulesStr := `~Usage:s/(\d+)/${1}ms/{*duration_seconds;*round:1:*middle}(2.2)`
|
||||
rsrField, err := NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eOut := "2.2"
|
||||
if out, err := rsrField.Parse("2210"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
rulesStr = `~Usage:s/(\d+)/${1}ms/{*duration_seconds;*round:1:*middle}(2.21)`
|
||||
rsrField, _ = NewRSRField(rulesStr)
|
||||
if _, err := rsrField.Parse("2210"); err == nil || err.Error() != "filter not passing" {
|
||||
t.Error(err)
|
||||
}
|
||||
rulesStr = `~Usage:s/(\d+)/${1}ms/{*duration_seconds;*round}`
|
||||
if rsrField, err = NewRSRField(rulesStr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eOut = "2"
|
||||
if out, err := rsrField.Parse("2210"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
rulesStr = `Usage{*duration_seconds}`
|
||||
rsrField, err = NewRSRField(rulesStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eOut = "10"
|
||||
if out, err := rsrField.Parse("10000000000"); err != nil {
|
||||
t.Error(err)
|
||||
} else if out != eOut {
|
||||
t.Errorf("expecting: %s, received: %s", eOut, out)
|
||||
}
|
||||
}
|
||||
180
utils/rsrfilters.go
Normal file
180
utils/rsrfilters.go
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewRSRFilter instantiates a new RSRFilter, setting it's properties
|
||||
func NewRSRFilter(fltrVal string) (rsrFltr *RSRFilter, err error) {
|
||||
rsrFltr = new(RSRFilter)
|
||||
if fltrVal == "" {
|
||||
return rsrFltr, nil
|
||||
}
|
||||
if fltrVal[:1] == NegativePrefix {
|
||||
rsrFltr.negative = true
|
||||
fltrVal = fltrVal[1:]
|
||||
if fltrVal == "" {
|
||||
return rsrFltr, nil
|
||||
}
|
||||
}
|
||||
rsrFltr.filterRule = fltrVal
|
||||
if fltrVal[:1] == DynamicDataPrefix {
|
||||
if rsrFltr.fltrRgxp, err = regexp.Compile(fltrVal[1:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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
|
||||
fltrRgxp *regexp.Regexp
|
||||
negative bool // Rule should not match
|
||||
}
|
||||
|
||||
func (rsrFltr *RSRFilter) FilterRule() string {
|
||||
return rsrFltr.filterRule
|
||||
}
|
||||
|
||||
func (rsrFltr *RSRFilter) Pass(val string) bool {
|
||||
if rsrFltr.filterRule == "" {
|
||||
return !rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule[:1] == DynamicDataPrefix {
|
||||
return rsrFltr.fltrRgxp.MatchString(val) != rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule == "^$" { // Special case to test empty value
|
||||
return len(val) == 0 != rsrFltr.negative
|
||||
}
|
||||
if rsrFltr.filterRule[:1] == MatchStartPrefix {
|
||||
if rsrFltr.filterRule[len(rsrFltr.filterRule)-1:] == MatchEndPrefix { // starts with ^ and ends with $, exact match
|
||||
return val == rsrFltr.filterRule[1:len(rsrFltr.filterRule)-1] != rsrFltr.negative
|
||||
}
|
||||
return strings.HasPrefix(val, rsrFltr.filterRule[1:]) != rsrFltr.negative
|
||||
}
|
||||
lastIdx := len(rsrFltr.filterRule) - 1
|
||||
if rsrFltr.filterRule[lastIdx:] == MatchEndPrefix {
|
||||
return strings.HasSuffix(val, rsrFltr.filterRule[:lastIdx]) != rsrFltr.negative
|
||||
}
|
||||
if len(rsrFltr.filterRule) > 2 && rsrFltr.filterRule[:2] == MatchGreaterThanOrEqual {
|
||||
gt, err := GreaterThan(StringToInterface(val),
|
||||
StringToInterface(rsrFltr.filterRule[2:]), true)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if len(rsrFltr.filterRule) > 2 && rsrFltr.filterRule[:2] == MatchLessThanOrEqual {
|
||||
gt, err := GreaterThan(StringToInterface(rsrFltr.filterRule[2:]), // compare the rule with the val
|
||||
StringToInterface(val),
|
||||
true)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if rsrFltr.filterRule[:1] == MatchGreaterThan {
|
||||
gt, err := GreaterThan(StringToInterface(val),
|
||||
StringToInterface(rsrFltr.filterRule[1:]), false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
|
||||
if rsrFltr.filterRule[:1] == MatchLessThan {
|
||||
gt, err := GreaterThan(StringToInterface(rsrFltr.filterRule[1:]), // compare the rule with the val
|
||||
StringToInterface(val),
|
||||
false)
|
||||
if err != nil {
|
||||
Logger.Warning(fmt.Sprintf("<RSRFilter> rule: <%s>, err: <%s>", rsrFltr.filterRule, err.Error()))
|
||||
return false
|
||||
}
|
||||
return gt != rsrFltr.negative
|
||||
}
|
||||
return (strings.Index(val, rsrFltr.filterRule) != -1) != rsrFltr.negative // default is string index
|
||||
}
|
||||
|
||||
func ParseRSRFilters(fldsStr, sep string) (RSRFilters, error) {
|
||||
if fldsStr == "" {
|
||||
return nil, nil
|
||||
}
|
||||
fltrSplt := strings.Split(fldsStr, sep)
|
||||
return ParseRSRFiltersFromSlice(fltrSplt)
|
||||
}
|
||||
|
||||
func ParseRSRFiltersFromSlice(fltrStrs []string) (RSRFilters, error) {
|
||||
rsrFltrs := make(RSRFilters, len(fltrStrs))
|
||||
for i, rlStr := range fltrStrs {
|
||||
if rsrFltr, err := NewRSRFilter(rlStr); err != nil {
|
||||
return nil, err
|
||||
} else if rsrFltr == nil {
|
||||
return nil, fmt.Errorf("Empty RSRFilter in rule: %s", rlStr)
|
||||
} else {
|
||||
rsrFltrs[i] = rsrFltr
|
||||
}
|
||||
}
|
||||
return rsrFltrs, nil
|
||||
}
|
||||
|
||||
type RSRFilters []*RSRFilter
|
||||
|
||||
func (fltrs RSRFilters) FilterRules() (rls string) {
|
||||
for _, fltr := range fltrs {
|
||||
rls += fltr.FilterRule()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// @all: specifies whether all filters should match or at least one
|
||||
func (fltrs RSRFilters) Pass(val string, allMustMatch bool) (matched bool) {
|
||||
if len(fltrs) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, fltr := range fltrs {
|
||||
matched = fltr.Pass(val)
|
||||
if allMustMatch {
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
} else if matched {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
353
utils/rsrfilters_test.go
Normal file
353
utils/rsrfilters_test.go
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRSRFilterPass(t *testing.T) {
|
||||
fltr, err := NewRSRFilter("") // Pass any
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("any") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!") // Pass nothing
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("any") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^full_match$") // Full string pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("full_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("full_match1") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^prefixMatch") // Prefix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("prefixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("prefixMatch12345") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("1prefixMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("suffixMatch$") // Suffix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("suffixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("12345suffixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("suffixMatch1") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!fullMatch") // Negative full pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("ShouldMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("fullMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!^prefixMatch") // Negative prefix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("prefixMatch123") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("123prefixMatch") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!suffixMatch$") // Negative suffix pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("123suffixMatch") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("suffixMatch123") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("~^C.+S$") // Regexp pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("CGRateS") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("1CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!~^C.*S$") // Negative regexp pass
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("^$") // Empty value
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("CGRateS") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!^$") // Non-empty value
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("CGRateS") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("indexed_match") // Indexed match
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("indexed_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("suf_indexed_match") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("indexed_match_pref") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if !fltr.Pass("suf_indexed_match_pref") {
|
||||
t.Error("Not passing!")
|
||||
}
|
||||
if fltr.Pass("indexed_matc") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
if fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
fltr, err = NewRSRFilter("!indexed_match") // Negative indexed match
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("indexed_match") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("suf_indexed_match") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("indexed_match_pref") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("suf_indexed_match_pref") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("indexed_matc") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("") {
|
||||
t.Error("Passing!")
|
||||
}
|
||||
|
||||
// compare greaterThan
|
||||
fltr, err = NewRSRFilter(">0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not greaterThan
|
||||
fltr, err = NewRSRFilter("!>0s") // !(n>0s)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare greaterThanOrEqual
|
||||
fltr, err = NewRSRFilter(">=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("-1s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not greaterThanOrEqual
|
||||
fltr, err = NewRSRFilter("!>=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("-1s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare lessThan
|
||||
fltr, err = NewRSRFilter("<0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("1ns") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("-12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
|
||||
// compare not lessThan
|
||||
fltr, err = NewRSRFilter("!<0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("1ns") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("-12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare lessThanOrEqual
|
||||
fltr, err = NewRSRFilter("<=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltr.Pass("-1s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("0s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if fltr.Pass("13") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("12s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
|
||||
// compare not lessThanOrEqual
|
||||
fltr, err = NewRSRFilter("!<=0s")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fltr.Pass("-1s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if fltr.Pass("0s") {
|
||||
t.Error("passing!")
|
||||
}
|
||||
if !fltr.Pass("13") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
if !fltr.Pass("12s") {
|
||||
t.Error("not passing!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRFiltersPass(t *testing.T) {
|
||||
rlStr := "~^C.+S$;CGRateS;ateS$"
|
||||
fltrs, err := ParseRSRFilters(rlStr, INFIELD_SEP)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !fltrs.Pass("CGRateS", true) {
|
||||
t.Error("Not passing")
|
||||
}
|
||||
if fltrs.Pass("ateS", true) {
|
||||
t.Error("Passing")
|
||||
}
|
||||
if !fltrs.Pass("ateS", false) {
|
||||
t.Error("Not passing")
|
||||
}
|
||||
if fltrs.Pass("teS", false) {
|
||||
t.Error("Passing")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user