mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
442 lines
12 KiB
Go
442 lines
12 KiB
Go
/*
|
|
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 Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/cgrates/birpc/context"
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
// NewFCTemplateFromFCTemplateJSONCfg creates a FCTemplate from json
|
|
func NewFCTemplateFromFCTemplateJSONCfg(jsnCfg *FcTemplateJsonCfg) (*FCTemplate, error) {
|
|
fcTmp := new(FCTemplate)
|
|
var err error
|
|
if jsnCfg.Type != nil {
|
|
fcTmp.Type = *jsnCfg.Type
|
|
}
|
|
if jsnCfg.Path != nil {
|
|
fcTmp.Path = *jsnCfg.Path
|
|
fcTmp.pathSlice = utils.CompilePath(fcTmp.Path)
|
|
fcTmp.Tag = fcTmp.Path
|
|
}
|
|
if jsnCfg.Tag != nil {
|
|
fcTmp.Tag = *jsnCfg.Tag
|
|
}
|
|
if jsnCfg.Filters != nil {
|
|
fcTmp.Filters = slices.Clone(*jsnCfg.Filters)
|
|
}
|
|
if jsnCfg.Value != nil {
|
|
if fcTmp.Value, err = utils.NewRSRParsers(*jsnCfg.Value, utils.RSRSep); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if jsnCfg.Width != nil {
|
|
fcTmp.Width = *jsnCfg.Width
|
|
}
|
|
if jsnCfg.Strip != nil {
|
|
fcTmp.Strip = *jsnCfg.Strip
|
|
}
|
|
if jsnCfg.Padding != nil {
|
|
fcTmp.Padding = *jsnCfg.Padding
|
|
}
|
|
if jsnCfg.Mandatory != nil {
|
|
fcTmp.Mandatory = *jsnCfg.Mandatory
|
|
}
|
|
if jsnCfg.Attribute_id != nil {
|
|
fcTmp.AttributeID = *jsnCfg.Attribute_id
|
|
}
|
|
if jsnCfg.New_branch != nil {
|
|
fcTmp.NewBranch = *jsnCfg.New_branch
|
|
}
|
|
if jsnCfg.Timezone != nil {
|
|
fcTmp.Timezone = *jsnCfg.Timezone
|
|
}
|
|
if jsnCfg.Blocker != nil {
|
|
fcTmp.Blocker = *jsnCfg.Blocker
|
|
}
|
|
fcTmp.Layout = time.RFC3339
|
|
if jsnCfg.Layout != nil {
|
|
fcTmp.Layout = *jsnCfg.Layout
|
|
}
|
|
if jsnCfg.Cost_shift_digits != nil {
|
|
fcTmp.CostShiftDigits = *jsnCfg.Cost_shift_digits
|
|
}
|
|
if jsnCfg.Rounding_decimals != nil {
|
|
fcTmp.RoundingDecimals = new(int)
|
|
*fcTmp.RoundingDecimals = *jsnCfg.Rounding_decimals
|
|
}
|
|
if jsnCfg.Mask_destinationd_id != nil {
|
|
fcTmp.MaskDestID = *jsnCfg.Mask_destinationd_id
|
|
}
|
|
if jsnCfg.Mask_length != nil {
|
|
fcTmp.MaskLen = *jsnCfg.Mask_length
|
|
}
|
|
return fcTmp, nil
|
|
}
|
|
|
|
// FCTemplate the teplate for a field
|
|
type FCTemplate struct {
|
|
Tag string
|
|
Type string // Type of field
|
|
Path string // Field identifier
|
|
Filters []string // list of filter profiles
|
|
Value utils.RSRParsers
|
|
Width int
|
|
Strip string
|
|
Padding string
|
|
Mandatory bool
|
|
AttributeID string // Used by NavigableMap when creating CGREvent/XMLElements
|
|
NewBranch bool // Used by NavigableMap when creating XMLElements
|
|
Timezone string
|
|
Blocker bool
|
|
Layout string // time format
|
|
CostShiftDigits int // Used for CDR
|
|
RoundingDecimals *int
|
|
MaskDestID string
|
|
MaskLen int
|
|
pathSlice []string // Field identifier
|
|
}
|
|
|
|
// FCTemplatesFromFCTemplatesJSONCfg will build a list of FCTemplates from json
|
|
func FCTemplatesFromFCTemplatesJSONCfg(jsnCfgFlds []*FcTemplateJsonCfg) (retFields []*FCTemplate, err error) {
|
|
retFields = make([]*FCTemplate, len(jsnCfgFlds))
|
|
for i, jsnFld := range jsnCfgFlds {
|
|
if retFields[i], err = NewFCTemplateFromFCTemplateJSONCfg(jsnFld); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// InflateTemplates will replace the *template fields with template content out msgTpls
|
|
func InflateTemplates(fcts []*FCTemplate, msgTpls FCTemplates) ([]*FCTemplate, error) {
|
|
var hasTpl bool
|
|
for i := 0; i < len(fcts); {
|
|
if fcts[i].Type == utils.MetaTemplate {
|
|
hasTpl = true
|
|
tplID, err := fcts[i].Value.ParseValue(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
refTpl, has := msgTpls[tplID]
|
|
if !has {
|
|
return nil, fmt.Errorf("no template with id: <%s>", tplID)
|
|
} else if len(refTpl) == 0 {
|
|
return nil, fmt.Errorf("empty template with id: <%s>", tplID)
|
|
}
|
|
wrkSlice := make([]*FCTemplate, len(refTpl)+len(fcts[i:])-1) // so we can cover tpls[i+1:]
|
|
copy(wrkSlice[:len(refTpl)], refTpl) // copy fields out of referenced template
|
|
if len(fcts[i:]) > 1 { // copy the rest of the fields after MetaTemplate
|
|
copy(wrkSlice[len(refTpl):], fcts[i+1:])
|
|
}
|
|
fcts = append(fcts[:i], wrkSlice...) // append the work
|
|
continue // don't increase index so we can recheck
|
|
}
|
|
i++
|
|
}
|
|
if !hasTpl {
|
|
return nil, nil
|
|
}
|
|
return fcts, nil
|
|
}
|
|
|
|
// Clone returns a deep copy of FCTemplate
|
|
func (fc FCTemplate) Clone() (cln *FCTemplate) {
|
|
cln = &FCTemplate{
|
|
Tag: fc.Tag,
|
|
Type: fc.Type,
|
|
Path: fc.Path,
|
|
Value: fc.Value.Clone(),
|
|
Width: fc.Width,
|
|
Strip: fc.Strip,
|
|
Padding: fc.Padding,
|
|
Mandatory: fc.Mandatory,
|
|
AttributeID: fc.AttributeID,
|
|
NewBranch: fc.NewBranch,
|
|
Timezone: fc.Timezone,
|
|
Blocker: fc.Blocker,
|
|
Layout: fc.Layout,
|
|
CostShiftDigits: fc.CostShiftDigits,
|
|
MaskDestID: fc.MaskDestID,
|
|
MaskLen: fc.MaskLen,
|
|
}
|
|
if fc.RoundingDecimals != nil {
|
|
cln.RoundingDecimals = utils.IntPointer(*fc.RoundingDecimals)
|
|
}
|
|
if fc.pathSlice != nil {
|
|
cln.pathSlice = slices.Clone(fc.pathSlice)
|
|
}
|
|
if fc.Filters != nil {
|
|
cln.Filters = slices.Clone(fc.Filters)
|
|
}
|
|
return
|
|
}
|
|
|
|
// FCTemplates the config for the templates
|
|
type FCTemplates map[string][]*FCTemplate
|
|
|
|
// loadTemplateSCfg loads the Template section of the configuration
|
|
func (sCft FCTemplates) Load(ctx *context.Context, jsnCfg ConfigDB, cfg *CGRConfig) (err error) {
|
|
jsnTemplateCfg := make(map[string][]*FcTemplateJsonCfg)
|
|
if err = jsnCfg.GetSection(ctx, TemplatesJSON, &jsnTemplateCfg); err != nil {
|
|
return
|
|
}
|
|
for k, val := range jsnTemplateCfg {
|
|
if sCft[k], err = FCTemplatesFromFCTemplatesJSONCfg(val); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// AsMapInterface returns the config as a map[string]any
|
|
func (sCft FCTemplates) AsMapInterface() any {
|
|
mp := make(map[string][]map[string]any)
|
|
for key, value := range sCft {
|
|
mp[key] = make([]map[string]any, len(value))
|
|
for i, item := range value {
|
|
mp[key][i] = item.AsMapInterface()
|
|
}
|
|
}
|
|
return mp
|
|
}
|
|
|
|
func (FCTemplates) SName() string { return TemplatesJSON }
|
|
func (sCft FCTemplates) CloneSection() Section { return sCft.Clone() }
|
|
|
|
// Clone returns a deep copy of FcTemplates
|
|
func (sCft FCTemplates) Clone() (cln FCTemplates) {
|
|
cln = make(FCTemplates)
|
|
for k, fcs := range sCft {
|
|
fcln := make([]*FCTemplate, len(fcs))
|
|
for i, fc := range fcs {
|
|
fcln[i] = fc.Clone()
|
|
}
|
|
cln[k] = fcln
|
|
}
|
|
return
|
|
}
|
|
|
|
// AsMapInterface returns the config as a map[string]any
|
|
func (fc *FCTemplate) AsMapInterface() (mp map[string]any) {
|
|
mp = make(map[string]any)
|
|
if fc.Tag != utils.EmptyString {
|
|
mp[utils.TagCfg] = fc.Tag
|
|
}
|
|
if fc.Type != utils.EmptyString {
|
|
mp[utils.TypeCf] = fc.Type
|
|
}
|
|
if fc.Path != utils.EmptyString {
|
|
mp[utils.PathCfg] = fc.Path
|
|
}
|
|
if fc.Filters != nil {
|
|
mp[utils.FiltersCfg] = fc.Filters
|
|
}
|
|
if fc.Value != nil {
|
|
mp[utils.ValueCfg] = fc.Value.GetRule()
|
|
}
|
|
if fc.Width != 0 {
|
|
mp[utils.WidthCfg] = fc.Width
|
|
}
|
|
if fc.Strip != utils.EmptyString {
|
|
mp[utils.StripCfg] = fc.Strip
|
|
}
|
|
if fc.Padding != utils.EmptyString {
|
|
mp[utils.PaddingCfg] = fc.Padding
|
|
}
|
|
if fc.Mandatory {
|
|
mp[utils.MandatoryCfg] = fc.Mandatory
|
|
}
|
|
if fc.AttributeID != utils.EmptyString {
|
|
mp[utils.AttributeIDCfg] = fc.AttributeID
|
|
}
|
|
if fc.NewBranch {
|
|
mp[utils.NewBranchCfg] = fc.NewBranch
|
|
}
|
|
if fc.Timezone != utils.EmptyString {
|
|
mp[utils.TimezoneCfg] = fc.Timezone
|
|
}
|
|
if fc.Blocker {
|
|
mp[utils.BlockerCfg] = fc.Blocker
|
|
}
|
|
if fc.Layout != time.RFC3339 {
|
|
mp[utils.LayoutCfg] = fc.Layout
|
|
}
|
|
if fc.CostShiftDigits != 0 {
|
|
mp[utils.CostShiftDigitsCfg] = fc.CostShiftDigits
|
|
}
|
|
if fc.RoundingDecimals != nil {
|
|
mp[utils.RoundingDecimalsCfg] = *fc.RoundingDecimals
|
|
}
|
|
if fc.MaskDestID != utils.EmptyString {
|
|
mp[utils.MaskDestIDCfg] = fc.MaskDestID
|
|
}
|
|
if fc.MaskLen != 0 {
|
|
mp[utils.MaskLenCfg] = fc.MaskLen
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetPathSlice returns the cached split of the path
|
|
func (fc *FCTemplate) GetPathSlice() []string {
|
|
return fc.pathSlice
|
|
}
|
|
|
|
// ComputePath used in test to populate private fields used to store the path
|
|
func (fc *FCTemplate) ComputePath() {
|
|
fc.pathSlice = utils.CompilePath(fc.Path)
|
|
}
|
|
|
|
func (fc *FCTemplate) Equals(fc2 *FCTemplate) bool {
|
|
return fc == nil && fc2 == nil ||
|
|
fc != nil && fc2 != nil &&
|
|
fc.Tag == fc2.Tag &&
|
|
fc.Type == fc2.Type &&
|
|
fc.Path == fc2.Path &&
|
|
slices.Equal(fc.Filters, fc2.Filters) &&
|
|
slices.Equal(fc.Value.AsStringSlice(), fc2.Value.AsStringSlice()) &&
|
|
fc.Width == fc2.Width &&
|
|
fc.Strip == fc2.Strip &&
|
|
fc.Padding == fc2.Padding &&
|
|
fc.Mandatory == fc2.Mandatory &&
|
|
fc.AttributeID == fc2.AttributeID &&
|
|
fc.NewBranch == fc2.NewBranch &&
|
|
fc.Timezone == fc2.Timezone &&
|
|
fc.Blocker == fc2.Blocker &&
|
|
fc.Layout == fc2.Layout &&
|
|
fc.CostShiftDigits == fc2.CostShiftDigits &&
|
|
fc.MaskDestID == fc2.MaskDestID &&
|
|
fc.MaskLen == fc2.MaskLen &&
|
|
(fc.RoundingDecimals == nil && fc2.RoundingDecimals == nil ||
|
|
fc.RoundingDecimals != nil && fc2.RoundingDecimals != nil &&
|
|
*fc.RoundingDecimals == *fc2.RoundingDecimals)
|
|
}
|
|
|
|
type FcTemplatesJsonCfg map[string][]*FcTemplateJsonCfg
|
|
type FcTemplateJsonCfg struct {
|
|
Tag *string
|
|
Type *string
|
|
Path *string
|
|
Attribute_id *string
|
|
Filters *[]string
|
|
Value *string
|
|
Width *int
|
|
Strip *string
|
|
Padding *string
|
|
Mandatory *bool
|
|
New_branch *bool
|
|
Timezone *string
|
|
Blocker *bool
|
|
Layout *string
|
|
Cost_shift_digits *int
|
|
Rounding_decimals *int
|
|
Mask_destinationd_id *string
|
|
Mask_length *int
|
|
}
|
|
|
|
func fcTemplatesEqual(v1, v2 []*FCTemplate) bool {
|
|
if len(v1) != len(v2) {
|
|
return false
|
|
}
|
|
for i := range v1 {
|
|
if !v1[i].Equals(v2[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func diffFcTemplateJsonCfg(d []*FcTemplateJsonCfg, v1, v2 []*FCTemplate) []*FcTemplateJsonCfg {
|
|
if !fcTemplatesEqual(v1, v2) {
|
|
d = make([]*FcTemplateJsonCfg, len(v2))
|
|
for i, v := range v2 {
|
|
d[i] = new(FcTemplateJsonCfg)
|
|
if v.Tag != utils.EmptyString {
|
|
d[i].Tag = utils.StringPointer(v.Tag)
|
|
}
|
|
if v.Type != utils.EmptyString {
|
|
d[i].Type = utils.StringPointer(v.Type)
|
|
}
|
|
if v.Path != utils.EmptyString {
|
|
d[i].Path = utils.StringPointer(v.Path)
|
|
}
|
|
if v.Filters != nil {
|
|
d[i].Filters = &v.Filters
|
|
}
|
|
if v.Value != nil {
|
|
d[i].Value = utils.StringPointer(v.Value.GetRule())
|
|
}
|
|
if v.Width != 0 {
|
|
d[i].Width = utils.IntPointer(v.Width)
|
|
}
|
|
if v.Strip != utils.EmptyString {
|
|
d[i].Strip = utils.StringPointer(v.Strip)
|
|
}
|
|
if v.Padding != utils.EmptyString {
|
|
d[i].Padding = utils.StringPointer(v.Padding)
|
|
}
|
|
if v.Mandatory {
|
|
d[i].Mandatory = utils.BoolPointer(v.Mandatory)
|
|
}
|
|
if v.AttributeID != utils.EmptyString {
|
|
d[i].Attribute_id = utils.StringPointer(v.AttributeID)
|
|
}
|
|
if v.NewBranch {
|
|
d[i].New_branch = utils.BoolPointer(v.NewBranch)
|
|
}
|
|
if v.Timezone != utils.EmptyString {
|
|
d[i].Timezone = utils.StringPointer(v.Timezone)
|
|
}
|
|
if v.Blocker {
|
|
d[i].Blocker = utils.BoolPointer(v.Blocker)
|
|
}
|
|
if v.Layout != time.RFC3339 {
|
|
d[i].Layout = utils.StringPointer(v.Layout)
|
|
}
|
|
if v.CostShiftDigits != 0 {
|
|
d[i].Cost_shift_digits = utils.IntPointer(v.CostShiftDigits)
|
|
}
|
|
if v.RoundingDecimals != nil {
|
|
d[i].Rounding_decimals = utils.IntPointer(*v.RoundingDecimals)
|
|
}
|
|
if v.MaskDestID != utils.EmptyString {
|
|
d[i].Mask_destinationd_id = utils.StringPointer(v.MaskDestID)
|
|
}
|
|
if v.MaskLen != 0 {
|
|
d[i].Mask_length = utils.IntPointer(v.MaskLen)
|
|
}
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
func diffFcTemplatesJsonCfg(d FcTemplatesJsonCfg, v1, v2 FCTemplates) FcTemplatesJsonCfg {
|
|
if d == nil {
|
|
d = make(FcTemplatesJsonCfg)
|
|
}
|
|
for k, val := range v2 {
|
|
d[k] = diffFcTemplateJsonCfg(d[k], v1[k], val)
|
|
}
|
|
return d
|
|
}
|