diff --git a/config/config.go b/config/config.go index 62b0d6ff3..066a22f2b 100644 --- a/config/config.go +++ b/config/config.go @@ -84,33 +84,32 @@ type CGRConfig struct { RaterBalancer string // balancer address host:port BalancerEnabled bool SchedulerEnabled bool - CDRSEnabled bool // Enable CDR Server service - CDRSExtraFields []string // Extra fields to store in CDRs - CDRSSearchReplaceRules map[string]*utils.ReSearchReplace // Key will be the field name, values their compiled ReSearchReplace rules - CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal> - CdreCdrFormat string // Format of the exported CDRs. - CdreExtraFields []string // Extra fields list to add in exported CDRs - CdreDir string // Path towards exported cdrs directory - CdrcEnabled bool // Enable CDR client functionality - CdrcCdrs string // Address where to reach CDR server - CdrcCdrsMethod string // Mechanism to use when posting CDRs on server - CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify - CdrcCdrType string // CDR file format . - CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored. - CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved. - CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database. - CdrcAccIdField string // Accounting id field identifier. Use index number in case of .csv cdrs. - CdrcReqTypeField string // Request type field identifier. Use index number in case of .csv cdrs. - CdrcDirectionField string // Direction field identifier. Use index numbers in case of .csv cdrs. - CdrcTenantField string // Tenant field identifier. Use index numbers in case of .csv cdrs. - CdrcTorField string // Type of Record field identifier. Use index numbers in case of .csv cdrs. - CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs. - CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs. - CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs. - CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs. - CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs. - CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs. - CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2" + CDRSEnabled bool // Enable CDR Server service + CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs + CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal> + CdreCdrFormat string // Format of the exported CDRs. + CdreExtraFields []string // Extra fields list to add in exported CDRs + CdreDir string // Path towards exported cdrs directory + CdrcEnabled bool // Enable CDR client functionality + CdrcCdrs string // Address where to reach CDR server + CdrcCdrsMethod string // Mechanism to use when posting CDRs on server + CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify + CdrcCdrType string // CDR file format . + CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored. + CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved. + CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database. + CdrcAccIdField string // Accounting id field identifier. Use index number in case of .csv cdrs. + CdrcReqTypeField string // Request type field identifier. Use index number in case of .csv cdrs. + CdrcDirectionField string // Direction field identifier. Use index numbers in case of .csv cdrs. + CdrcTenantField string // Tenant field identifier. Use index numbers in case of .csv cdrs. + CdrcTorField string // Type of Record field identifier. Use index numbers in case of .csv cdrs. + CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs. + CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs. + CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs. + CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs. + CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs. + CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs. + CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2" SMEnabled bool SMSwitchType string SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer @@ -190,7 +189,7 @@ func (self *CGRConfig) setDefaults() error { self.BalancerEnabled = false self.SchedulerEnabled = false self.CDRSEnabled = false - self.CDRSExtraFields = []string{} + self.CDRSExtraFields = []*utils.RSRField{} self.CDRSMediator = "" self.CdreCdrFormat = "csv" self.CdreExtraFields = []string{} @@ -436,8 +435,11 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { cfg.CDRSEnabled, _ = c.GetBool("cdrs", "enabled") } if hasOpt = c.HasOption("cdrs", "extra_fields"); hasOpt { - if cfg.CDRSExtraFields, errParse = ConfigSlice(c, "cdrs", "extra_fields"); errParse != nil { + extraFieldsStr, _ := c.GetString("cdrs", "extra_fields") + if extraFields, err := ParseRSRFields(extraFieldsStr); err != nil { return nil, errParse + } else { + cfg.CDRSExtraFields = extraFields } } if hasOpt = c.HasOption("cdrs", "mediator"); hasOpt { diff --git a/config/config_test.go b/config/config_test.go index d9f3f539f..897eea1a8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -78,7 +78,7 @@ func TestDefaults(t *testing.T) { eCfg.BalancerEnabled = false eCfg.SchedulerEnabled = false eCfg.CDRSEnabled = false - eCfg.CDRSExtraFields = []string{} + eCfg.CDRSExtraFields = []*utils.RSRField{} eCfg.CDRSMediator = "" eCfg.CdreCdrFormat = "csv" eCfg.CdreExtraFields = []string{} @@ -211,7 +211,7 @@ func TestConfigFromFile(t *testing.T) { eCfg.BalancerEnabled = true eCfg.SchedulerEnabled = true eCfg.CDRSEnabled = true - eCfg.CDRSExtraFields = []string{"test"} + eCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}} eCfg.CDRSMediator = "test" eCfg.CdreCdrFormat = "test" eCfg.CdreExtraFields = []string{"test"} diff --git a/config/helpers.go b/config/helpers.go index 646ebc30f..0e6f97085 100644 --- a/config/helpers.go +++ b/config/helpers.go @@ -21,7 +21,10 @@ package config import ( "code.google.com/p/goconf/conf" "errors" + "regexp" "strings" + + "github.com/cgrates/cgrates/utils" ) // Adds support for slice values in config @@ -42,3 +45,43 @@ func ConfigSlice(c *conf.ConfigFile, section, valName string) ([]string, error) } return cfgValStrs, nil } + +// Used to parse extra fields definition +func parseSearchReplaceFromFieldRule(fieldRule string) (string, *utils.ReSearchReplace, error) { + // String rule expected in the form ~hdr_name:s/match_rule/replace_rule/ + getRuleRgxp := regexp.MustCompile(`~(\w+):s\/(.+[^\\])\/(.+[^\\])\/`) // Make sure the separator / is not escaped in the rule + allMatches := getRuleRgxp.FindStringSubmatch(fieldRule) + if len(allMatches) != 4 { // Second and third groups are of interest to us + return "", nil, errors.New("Invalid Search&Replace field rule.") + } + fieldName := allMatches[1] + searchRegexp, err := regexp.Compile(allMatches[2]) + if err != nil { + return fieldName, nil, err + } + return fieldName, &utils.ReSearchReplace{searchRegexp, allMatches[3]}, nil +} + +func ParseRSRFields(configVal string) ([]*utils.RSRField, error) { + cfgValStrs := strings.Split(configVal, string(utils.CSV_SEP)) + if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value + return []*utils.RSRField{}, nil + } + rsrFields := make([]*utils.RSRField, len(cfgValStrs)) + for idx, cfgValStr := range cfgValStrs { + if len(cfgValStr) == 0 { //One empty element is presented when splitting empty string + return nil, errors.New("Empty values in config slice") + + } + if !strings.HasPrefix(cfgValStr, utils.REGEXP_SEP) { + rsrFields[idx] = &utils.RSRField{Id: cfgValStr} + continue // Nothing to be done for fields without ReSearchReplace rules + } + if fldId, reSrcRepl, err := parseSearchReplaceFromFieldRule(cfgValStr); err != nil { + return nil, err + } else { + rsrFields[idx] = &utils.RSRField{fldId, reSrcRepl} + } + } + return rsrFields, nil +} diff --git a/config/helpers_test.go b/config/helpers_test.go new file mode 100644 index 000000000..83d8b784f --- /dev/null +++ b/config/helpers_test.go @@ -0,0 +1,68 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2013 ITsysCOM + +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 config + +import ( + "reflect" + "regexp" + "testing" + + "github.com/cgrates/cgrates/utils" +) + +func TestParseSearchReplaceFromFieldRule(t *testing.T) { + // Normal case + fieldRule := `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/` + field, regSrchRplc, err := parseSearchReplaceFromFieldRule(fieldRule) + if len(field) == 0 || regSrchRplc == nil || err != nil { + t.Error("Failed parsing the field rule") + } else if !reflect.DeepEqual(regSrchRplc, &utils.ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)@`), "0$1"}) { + t.Error("Unexpected ReSearchReplace parsed") + } + // Missing ~ prefix + fieldRule = `sip_redirected_to:s/sip:\+49(\d+)@/0$1/` + if _, _, err := parseSearchReplaceFromFieldRule(fieldRule); err == nil { + t.Error("Parse error, field rule does not start with ~") + } + // Separator escaped + fieldRule = `~sip_redirected_to:s\/sip:\+49(\d+)@/0$1/` + if _, _, err := parseSearchReplaceFromFieldRule(fieldRule); err == nil { + t.Error("Parse error, field rule does not contain correct number of separators") + } + // One extra separator but escaped + fieldRule = `~sip_redirected_to:s/sip:\+49(\d+)\/@/0$1/` + field, regSrchRplc, err = parseSearchReplaceFromFieldRule(fieldRule) + if len(field) == 0 || regSrchRplc == nil || err != nil { + t.Error("Failed parsing the field rule") + } else if !reflect.DeepEqual(regSrchRplc, &utils.ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)\/@`), "0$1"}) { + t.Error("Unexpected ReSearchReplace parsed") + } +} + +func TestParseRSRFields(t *testing.T) { + fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination` + expectParsedFields := []*utils.RSRField{&utils.RSRField{Id: "host"}, + &utils.RSRField{Id: "sip_redirected_to", RSRule: &utils.ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)@`), "0$1"}}, + &utils.RSRField{Id: "destination"}} + if parsedFields, err := ParseRSRFields(fields); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if !reflect.DeepEqual(parsedFields, expectParsedFields) { + t.Errorf("Unexpected value of parsed fields") + } +} diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg index c41efc38c..2ef48accc 100644 --- a/data/conf/cgrates.cfg +++ b/data/conf/cgrates.cfg @@ -111,7 +111,6 @@ # answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>. # duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>. - [freeswitch] # server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket. # passwd = ClueCon # FreeSWITCH socket password. diff --git a/utils/consts.go b/utils/consts.go index b543d47f8..6acb85c3b 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -64,7 +64,7 @@ const ( COMMENT_CHAR = '#' CSV_SEP = ',' FALLBACK_SEP = ';' - REGEXP_SEP = '~' + REGEXP_SEP = "~" JSON = "json" MSGPACK = "msgpack" CSV_LOAD = "CSVLOAD" diff --git a/utils/researchreplace.go b/utils/researchreplace.go index be0acbfaf..51126c7aa 100644 --- a/utils/researchreplace.go +++ b/utils/researchreplace.go @@ -19,7 +19,6 @@ along with this program. If not, see package utils import ( - "errors" "regexp" ) @@ -35,19 +34,3 @@ func (self *ReSearchReplace) Process(source string) string { res = self.SearchRegexp.ExpandString(res, self.ReplaceTemplate, source, match) return string(res) } - -// Used to parse extra fields definition -func ParseSearchReplaceFromFieldRule(fieldRule string) (string, *ReSearchReplace, error) { - // String rule expected in the form ~hdr_name:s/match_rule/replace_rule/ - getRuleRgxp := regexp.MustCompile(`~(\w+):s\/(.+[^\\])\/(.+[^\\])\/`) // Make sure the separator / is not escaped in the rule - allMatches := getRuleRgxp.FindStringSubmatch(fieldRule) - if len(allMatches) != 4 { // Second and third groups are of interest to us - return "", nil, errors.New("Invalid Search&Replace field rule.") - } - fieldName := allMatches[1] - searchRegexp, err := regexp.Compile(allMatches[2]) - if err != nil { - return fieldName, nil, err - } - return fieldName, &ReSearchReplace{searchRegexp, allMatches[3]}, nil -} diff --git a/utils/researchreplace_test.go b/utils/researchreplace_test.go index bb98ff3ba..87fc961f3 100644 --- a/utils/researchreplace_test.go +++ b/utils/researchreplace_test.go @@ -19,7 +19,6 @@ along with this program. If not, see package utils import ( - "reflect" "regexp" "testing" ) @@ -32,32 +31,3 @@ func TestProcessReSearchReplace(t *testing.T) { t.Error("Unexpected output from SearchReplace: ", outStr) } } - -func TestParseSearchReplaceFromFieldRule(t *testing.T) { - // Normal case - fieldRule := `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/` - field, regSrchRplc, err := ParseSearchReplaceFromFieldRule(fieldRule) - if len(field) == 0 || regSrchRplc == nil || err != nil { - t.Error("Failed parsing the field rule") - } else if !reflect.DeepEqual(regSrchRplc, &ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)@`), "0$1"}) { - t.Error("Unexpected ReSearchReplace parsed") - } - // Missing ~ prefix - fieldRule = `sip_redirected_to:s/sip:\+49(\d+)@/0$1/` - if _, _, err := ParseSearchReplaceFromFieldRule(fieldRule); err == nil { - t.Error("Parse error, field rule does not start with ~") - } - // Separator escaped - fieldRule = `~sip_redirected_to:s\/sip:\+49(\d+)@/0$1/` - if _, _, err := ParseSearchReplaceFromFieldRule(fieldRule); err == nil { - t.Error("Parse error, field rule does not contain correct number of separators") - } - // One extra separator but escaped - fieldRule = `~sip_redirected_to:s/sip:\+49(\d+)\/@/0$1/` - field, regSrchRplc, err = ParseSearchReplaceFromFieldRule(fieldRule) - if len(field) == 0 || regSrchRplc == nil || err != nil { - t.Error("Failed parsing the field rule") - } else if !reflect.DeepEqual(regSrchRplc, &ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)\/@`), "0$1"}) { - t.Error("Unexpected ReSearchReplace parsed") - } -} diff --git a/utils/rsrfield.go b/utils/rsrfield.go new file mode 100644 index 000000000..540fced07 --- /dev/null +++ b/utils/rsrfield.go @@ -0,0 +1,32 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2013 ITsysCOM + +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 + +type RSRField struct { + Id string // Identifier + RSRule *ReSearchReplace // Rule to use when processing field value +} + +// Parse the field value from a string +func (rsrf *RSRField) ParseValue(value string) string { + if rsrf.RSRule != nil { + value = rsrf.RSRule.Process(value) + } + return value +}