Files
cgrates/utils/dataconverter.go
2025-04-25 16:24:43 +02:00

841 lines
23 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 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 (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"net"
"reflect"
"strconv"
"strings"
"time"
"github.com/cgrates/sipingo"
"github.com/nyaruka/phonenumbers"
)
// DataConverters groups together multiple converters,
// executing optimized conversions
type DataConverters []DataConverter
// ConvertString converts from and to string
func (dcs DataConverters) ConvertString(in string) (out string, err error) {
outIface := any(in)
for _, cnv := range dcs {
if outIface, err = cnv.Convert(outIface); err != nil {
return
}
}
return IfaceAsString(outIface), nil
}
// ConvertString converts from and to string
func (dcs DataConverters) ConvertInterface(in any) (out any, err error) {
out = in
for _, cnv := range dcs {
if out, err = cnv.Convert(out); err != nil {
return
}
}
return
}
// DataConverter represents functions which should convert input into output
type DataConverter interface {
Convert(any) (any, error)
}
// NewDataConverter is a factory of converters
func NewDataConverter(params string) (conv DataConverter, err error) {
switch {
case params == MetaDurationSeconds:
return NewDurationSecondsConverter(EmptyString)
case params == MetaDurationNanoseconds:
return NewDurationNanosecondsConverter(EmptyString)
case strings.HasPrefix(params, MetaRound):
if len(params) == len(MetaRound) { // no extra params, defaults implied
return NewRoundConverter(EmptyString)
}
return NewRoundConverter(params[len(MetaRound)+1:])
case strings.HasPrefix(params, MetaMultiply):
if len(params) == len(MetaMultiply) { // no extra params, defaults implied
return NewMultiplyConverter(EmptyString)
}
return NewMultiplyConverter(params[len(MetaMultiply)+1:])
case strings.HasPrefix(params, MetaDivide):
if len(params) == len(MetaDivide) { // no extra params, defaults implied
return NewDivideConverter(EmptyString)
}
return NewDivideConverter(params[len(MetaDivide)+1:])
case params == MetaJSON:
return new(JSONConverter), nil
case params == MetaDuration:
return NewDurationConverter(EmptyString)
case params == MetaIP2Hex:
return new(IP2HexConverter), nil
case params == MetaString2Hex:
return new(String2HexConverter), nil
case params == MetaSIPURIHost:
return new(SIPURIHostConverter), nil
case params == MetaSIPURIUser:
return new(SIPURIUserConverter), nil
case params == MetaSIPURIMethod:
return new(SIPURIMethodConverter), nil
case params == MetaUnixTime:
return new(UnixTimeConverter), nil
case params == MetaLen:
return new(LengthConverter), nil
case params == MetaSlice:
return new(SliceConverter), nil
case params == MetaFloat64:
return new(Float64Converter), nil
case params == E164DomainConverter:
return new(e164DomainConverter), nil
case params == E164Converter:
return new(e164Converter), nil
case strings.HasPrefix(params, MetaLibPhoneNumber):
if len(params) == len(MetaLibPhoneNumber) {
return NewPhoneNumberConverter(EmptyString)
}
return NewPhoneNumberConverter(params[len(MetaLibPhoneNumber)+1:])
case strings.HasPrefix(params, MetaTimeString):
layout := time.RFC3339
if len(params) > len(MetaTimeString) { // no extra params, defaults implied
layout = params[len(MetaTimeString)+1:]
}
return NewTimeStringConverter(layout), nil
case strings.HasPrefix(params, MetaRandom):
if len(params) == len(MetaRandom) { // no extra params, defaults implied
return NewRandomConverter(EmptyString)
}
return NewRandomConverter(params[len(MetaRandom)+1:])
case strings.HasPrefix(params, MetaJoin):
if len(params) == len(MetaJoin) { // no extra params, defaults implied
return joinConverter(InfieldSep), nil
}
return joinConverter(params[len(MetaJoin)+1:]), nil
case strings.HasPrefix(params, MetaSplit):
if len(params) == len(MetaSplit) { // no extra params, defaults implied
return splitConverter(InfieldSep), nil
}
return splitConverter(params[len(MetaSplit)+1:]), nil
case strings.HasPrefix(params, MetaStrip):
return NewStripConverter(params)
case strings.HasPrefix(params, MetaGigawords):
return new(GigawordsConverter), nil
default:
return nil, fmt.Errorf("unsupported converter definition: <%s>", params)
}
}
func NewDataConverterMustCompile(params string) (conv DataConverter) {
var err error
if conv, err = NewDataConverter(params); err != nil {
panic(fmt.Sprintf("parsing: <%s>, error: %s", params, err.Error()))
}
return
}
func NewDurationSecondsConverter(params string) (hdlr DataConverter, err error) {
return new(DurationSecondsConverter), nil
}
// DurationSecondsConverter converts duration into seconds encapsulated in float64
type DurationSecondsConverter struct{}
func (mS *DurationSecondsConverter) Convert(in any) (
out any, err error) {
var inDur time.Duration
if inDur, err = IfaceAsDuration(in); err != nil {
return nil, err
}
out = inDur.Seconds()
return
}
func NewDurationNanosecondsConverter(params string) (
hdlr DataConverter, err error) {
return new(DurationNanosecondsConverter), nil
}
// DurationNanosecondsConverter converts duration into nanoseconds encapsulated in int64
type DurationNanosecondsConverter struct{}
func (mS *DurationNanosecondsConverter) Convert(in any) (
out any, err error) {
var inDur time.Duration
if inDur, err = IfaceAsDuration(in); err != nil {
return nil, err
}
out = inDur.Nanoseconds()
return
}
func NewRoundConverter(params string) (hdlr DataConverter, err error) {
rc := new(RoundConverter)
var paramsSplt []string
if params != EmptyString {
paramsSplt = strings.Split(params, InInFieldSep)
}
switch len(paramsSplt) {
case 0:
rc.Method = MetaRoundingMiddle
case 1:
if rc.Decimals, err = strconv.Atoi(paramsSplt[0]); err != nil {
return nil, fmt.Errorf("%s converter needs integer as decimals, have: <%s>",
MetaRound, paramsSplt[0])
}
rc.Method = MetaRoundingMiddle
case 2:
rc.Method = paramsSplt[1]
if rc.Decimals, err = strconv.Atoi(paramsSplt[0]); err != nil {
return nil, fmt.Errorf("%s converter needs integer as decimals, have: <%s>",
MetaRound, paramsSplt[0])
}
default:
return nil, fmt.Errorf("unsupported %s converter parameters: <%s>",
MetaRound, params)
}
return rc, nil
}
// RoundConverter will round floats
type RoundConverter struct {
Decimals int
Method string
}
func (rnd *RoundConverter) Convert(in any) (out any, err error) {
var inFloat float64
if inFloat, err = IfaceAsFloat64(in); err != nil {
return
}
out = Round(inFloat, rnd.Decimals, rnd.Method)
return
}
func NewMultiplyConverter(constructParams string) (hdlr DataConverter, err error) {
if constructParams == EmptyString {
return nil, ErrMandatoryIeMissingNoCaps
}
var val float64
if val, err = strconv.ParseFloat(constructParams, 64); err != nil {
return
}
return &MultiplyConverter{Value: val}, nil
}
// MultiplyConverter multiplies input with value in params
// encapsulates the output as float64 value
type MultiplyConverter struct {
Value float64
}
func (m *MultiplyConverter) Convert(in any) (out any, err error) {
var inFloat64 float64
if inFloat64, err = IfaceAsFloat64(in); err != nil {
return nil, err
}
out = inFloat64 * m.Value
return
}
func NewDivideConverter(constructParams string) (hdlr DataConverter, err error) {
if constructParams == EmptyString {
return nil, ErrMandatoryIeMissingNoCaps
}
var val float64
if val, err = strconv.ParseFloat(constructParams, 64); err != nil {
return
}
return &DivideConverter{Value: val}, nil
}
// DivideConverter divides input with value in params
// encapsulates the output as float64 value
type DivideConverter struct {
Value float64
}
func (m *DivideConverter) Convert(in any) (out any, err error) {
var inFloat64 float64
if inFloat64, err = IfaceAsFloat64(in); err != nil {
return nil, err
}
out = inFloat64 / m.Value
return
}
func NewDurationConverter(params string) (hdlr DataConverter, err error) {
return new(DurationConverter), nil
}
// DurationConverter converts duration into seconds encapsulated in float64
type DurationConverter struct{}
func (mS *DurationConverter) Convert(in any) (
out any, err error) {
return IfaceAsDuration(in)
}
// NewPhoneNumberConverter create a new phoneNumber converter
// If the format isn't specify by default we use NATIONAL
// Possible fromats are : E164(0) , INTERNATIONAL(1) , NATIONAL(2) ,RFC3966(3)
// Also ContryCode needs to be specified
func NewPhoneNumberConverter(params string) (
pbDC DataConverter, err error) {
lc := new(PhoneNumberConverter)
var paramsSplt []string
if params != EmptyString {
paramsSplt = strings.Split(params, InInFieldSep)
}
switch len(paramsSplt) {
case 2:
lc.CountryCode = paramsSplt[0]
frm, err := strconv.Atoi(paramsSplt[1])
if err != nil {
return nil, err
}
lc.Format = phonenumbers.PhoneNumberFormat(frm)
case 1:
lc.CountryCode = paramsSplt[0]
lc.Format = 2
default:
return nil, fmt.Errorf("unsupported %s converter parameters: <%s>",
MetaLibPhoneNumber, params)
}
return lc, nil
}
// PhoneNumberConverter converts
type PhoneNumberConverter struct {
CountryCode string
Format phonenumbers.PhoneNumberFormat
}
func (lc *PhoneNumberConverter) Convert(in any) (out any, err error) {
num, err := phonenumbers.Parse(IfaceAsString(in), lc.CountryCode)
if err != nil {
return nil, err
}
return phonenumbers.Format(num, lc.Format), nil
}
// IP2HexConverter will transform ip to hex
type IP2HexConverter struct{}
// Convert implements DataConverter interface
func (*IP2HexConverter) Convert(in any) (out any, err error) {
var ip net.IP
switch val := in.(type) {
case string:
ip = net.ParseIP(val)
case net.IP:
ip = val
default:
src := IfaceAsString(in)
ip = net.ParseIP(src)
}
hx := hex.EncodeToString([]byte(ip))
if len(hx) < 8 {
return hx, nil
}
return "0x" + string([]byte(hx)[len(hx)-8:]), nil
}
// SIPURIHostConverter will return the
type SIPURIHostConverter struct{}
// Convert implements DataConverter interface
func (*SIPURIHostConverter) Convert(in any) (out any, err error) {
return sipingo.HostFrom(IfaceAsString(in)), nil
}
// SIPURIUserConverter will return the
type SIPURIUserConverter struct{}
// Convert implements DataConverter interface
func (*SIPURIUserConverter) Convert(in any) (out any, err error) {
return sipingo.UserFrom(IfaceAsString(in)), nil
}
// SIPURIMethodConverter will return the
type SIPURIMethodConverter struct{}
// Convert implements DataConverter interface
func (*SIPURIMethodConverter) Convert(in any) (out any, err error) {
return sipingo.MethodFrom(IfaceAsString(in)), nil
}
func NewTimeStringConverter(params string) (hdlr DataConverter) {
return &TimeStringConverter{Layout: params}
}
type TimeStringConverter struct {
Layout string
}
// Convert implements DataConverter interface
func (tS *TimeStringConverter) Convert(in any) (
out any, err error) {
tm, err := ParseTimeDetectLayout(in.(string), EmptyString)
if err != nil {
return nil, err
}
return tm.Format(tS.Layout), nil
}
// String2HexConverter will transform the string to hex
type String2HexConverter struct{}
// Convert implements DataConverter interface
func (*String2HexConverter) Convert(in any) (o any, err error) {
var out string
if out = hex.EncodeToString([]byte(IfaceAsString(in))); len(out) == 0 {
o = out
return
}
o = "0x" + out
return
}
// UnixTimeConverter converts the interface in the unix time
type UnixTimeConverter struct{}
// Convert implements DataConverter interface
func (tS *UnixTimeConverter) Convert(in any) (
out any, err error) {
var tm time.Time
if tm, err = ParseTimeDetectLayout(in.(string), EmptyString); err != nil {
return
}
out = tm.Unix()
return
}
func NewRandomConverter(params string) (dflr DataConverter, err error) {
randConv := &RandomConverter{}
if params == EmptyString {
dflr = randConv
return
}
sls := strings.Split(params, InInFieldSep)
switch len(sls) {
case 2:
if sls[0] != EmptyString {
if randConv.begin, err = strconv.Atoi(sls[0]); err != nil {
return
}
}
if sls[1] != EmptyString {
if randConv.end, err = strconv.Atoi(sls[1]); err != nil {
return
}
}
case 1:
if randConv.begin, err = strconv.Atoi(sls[0]); err != nil {
return
}
}
dflr = randConv
return
}
type RandomConverter struct {
begin, end int
}
// Convert implements DataConverter interface
func (rC *RandomConverter) Convert(in any) (
out any, err error) {
if rC.begin == 0 {
if rC.end == 0 {
return rand.Int(), nil
} else {
return rand.Intn(rC.end), nil
}
} else {
if rC.end == 0 {
return rand.Int() + rC.begin, nil
} else {
return int(RandomInteger(int64(rC.begin), int64(rC.end))), nil
}
}
}
// LengthConverter returns the lenght of the slice
type LengthConverter struct{}
// Convert implements DataConverter interface
func (LengthConverter) Convert(in any) (out any, err error) {
switch val := in.(type) {
case string:
if len(val) >= 2 {
var tmp any
var l func() (any, error)
switch {
case val[0] == '[' && val[len(val)-1] == ']':
v := []any{}
l = func() (any, error) { return len(v), nil }
tmp = &v
case val[0] == '{' && val[len(val)-1] == '}':
v := map[string]any{}
l = func() (any, error) { return len(v), nil }
tmp = &v
}
if tmp != nil {
if err := json.Unmarshal([]byte(val), tmp); err == nil {
return l()
}
}
}
return len(val), nil
case []string:
return len(val), nil
case []any:
return len(val), nil
case []bool:
return len(val), nil
case []int:
return len(val), nil
case []int8:
return len(val), nil
case []int16:
return len(val), nil
case []int32:
return len(val), nil
case []int64:
return len(val), nil
case []uint:
return len(val), nil
case []uint8:
return len(val), nil
case []uint16:
return len(val), nil
case []uint32:
return len(val), nil
case []uint64:
return len(val), nil
case []uintptr:
return len(val), nil
case []float32:
return len(val), nil
case []float64:
return len(val), nil
case []complex64:
return len(val), nil
case []complex128:
return len(val), nil
case map[string]string:
return len(val), nil
case map[string]any:
return len(val), nil
case map[string]bool:
return len(val), nil
case map[string]int:
return len(val), nil
case map[string]int8:
return len(val), nil
case map[string]int16:
return len(val), nil
case map[string]int32:
return len(val), nil
case map[string]int64:
return len(val), nil
case map[string]uint:
return len(val), nil
case map[string]uint8:
return len(val), nil
case map[string]uint16:
return len(val), nil
case map[string]uint32:
return len(val), nil
case map[string]uint64:
return len(val), nil
case map[string]uintptr:
return len(val), nil
case map[string]float32:
return len(val), nil
case map[string]float64:
return len(val), nil
case map[string]complex64:
return len(val), nil
case map[string]complex128:
return len(val), nil
default:
vl := reflect.ValueOf(val)
switch vl.Kind() {
case reflect.Array, reflect.Map, reflect.Slice:
return vl.Len(), nil
default:
return len(IfaceAsString(val)), nil
}
}
}
// SliceConverter converts the interface in the unix time
type SliceConverter struct{}
// Convert implements DataConverter interface
func (SliceConverter) Convert(in any) (out any, err error) {
switch val := in.(type) {
case []string,
[]any,
[]bool,
[]int,
[]int8,
[]int16,
[]int32,
[]int64,
[]uint,
[]uint8,
[]uint16,
[]uint32,
[]uint64,
[]uintptr,
[]float32,
[]float64,
[]complex64,
[]complex128:
return val, nil
default:
src := IfaceAsString(in)
if strings.HasPrefix(src, IdxStart) &&
strings.HasSuffix(src, IdxEnd) { // it has a similar structure to a json marshaled slice
var slice []any
if err := json.Unmarshal([]byte(src), &slice); err == nil { // no error when unmarshal safe to asume that this is a slice
return slice, nil
}
}
return src, nil
}
}
type Float64Converter struct{}
// Convert implements DataConverter interface
func (Float64Converter) Convert(in any) (any, error) {
return IfaceAsFloat64(in)
}
// e164DomainConverter extracts the domain part out of a NAPTR name record
type e164DomainConverter struct{}
func (e164DomainConverter) Convert(in any) (any, error) {
name := IfaceAsString(in)
if i := strings.Index(name, ".e164."); i != -1 {
name = name[i:]
}
return strings.Trim(name, "."), nil
}
// e164Converter extracts the E164 address out of a NAPTR name record
type e164Converter struct{}
func (e164Converter) Convert(in any) (any, error) {
name := IfaceAsString(in)
i := strings.Index(name, ".e164.")
if i == -1 {
return nil, errors.New("unknown format")
}
return ReverseString(
strings.Replace(name[:i], ".", "", -1)), nil
}
type joinConverter string
func (j joinConverter) Convert(in any) (any, error) {
slice, err := IfaceAsStringSlice(in)
if err != nil {
return nil, err
}
return strings.Join(slice, string(j)), nil
}
type splitConverter string
func (j splitConverter) Convert(in any) (any, error) {
return strings.Split(IfaceAsString(in), string(j)), nil
}
// JSONConverter converts an object to json string
type JSONConverter struct{}
func (jsnC JSONConverter) Convert(in any) (any, error) {
b, err := json.Marshal(in)
if err != nil {
return EmptyString, err
}
return string(b), nil
}
// StripConverter strips the prefix, the suffix or both from a string.
type StripConverter struct {
side string // side represents which part of the string to strip: prefix, suffix, or both.
substr string // substr represents the substring to be removed from the string.
amount int // amount represents the number of characters to be removed from the string.
}
// NewStripConverter initializes and returns a new StripConverter with configurations
// based on the provided parameters in the input string. Each parameter in the input
// string should be separated by ':'.
//
// The input string must follow one of the following formats:
// 1. "*strip:<side>:<amount>"
// 2. "*strip:<side>:<substring>[:<amount>]"
// 3. "*strip:<side>:*char:<substring>[:<amount>]"
//
// Explanation of placeholders:
// - <side>: Specifies which part of the string to strip. Must be one of "*prefix", "*suffix", or "*both".
// - <substring>: Identifies the substring to remove. It can be a specific string, "*nil" for null characters,
// "*space" for spaces, or any other character.
// - <amount> (optional): Determines the number of characters to remove. If omitted, all instances of <substring>
// are removed.
//
// Examples:
// - "*strip:*prefix:5": Removes the first 5 characters from the string's prefix.
// - "*strip:*suffix:*nil": Eliminates all trailing null characters in the string.
// - "*strip:*both:*space:2": Clears 2 spaces from both the prefix and suffix of the string.
// - "*strip:*suffix:*char:abc": Removes the substring "abc" from the suffix of the string.
// - "*strip:*prefix:*char:abc:2": Strips the substring "abc" from the prefix of the string, repeated 2 times.
func NewStripConverter(params string) (DataConverter, error) {
paramSlice := strings.Split(params, InInFieldSep)
paramCount := len(paramSlice)
if paramCount < 3 || paramCount > 5 {
return nil, errors.New("strip converter: invalid number of parameters (should have 3, 4 or 5)")
}
sc := StripConverter{
side: paramSlice[1],
substr: paramSlice[2],
amount: -1,
}
var err error
switch sc.substr {
case EmptyString:
return nil, errors.New("strip converter: substr parameter cannot be empty")
case MetaNil, MetaSpace:
if paramCount == 5 {
return nil, errors.New("strip converter: cannot have 5 params in *nil/*space case")
}
if sc.substr == MetaNil {
sc.substr = "\u0000"
} else {
sc.substr = " "
}
if paramCount == 4 {
sc.amount, err = strconv.Atoi(paramSlice[3])
if err != nil {
return nil, fmt.Errorf("strip converter: invalid amount parameter (%w)", err)
}
sc.substr = strings.Repeat(sc.substr, sc.amount)
}
case MetaChar:
if paramCount < 4 || paramSlice[3] == EmptyString {
return nil, errors.New("strip converter: usage of *char implies the need of 4 or 5 non-empty params")
}
sc.substr = paramSlice[3]
if paramCount == 5 {
sc.amount, err = strconv.Atoi(paramSlice[4])
if err != nil {
return nil, fmt.Errorf("strip converter: invalid amount parameter (%w)", err)
}
sc.substr = strings.Repeat(sc.substr, sc.amount)
}
default:
if paramCount > 3 {
return nil, errors.New("strip converter: just the amount specified, cannot have more than 3 params")
}
sc.amount, err = strconv.Atoi(paramSlice[2])
if err != nil {
return nil, fmt.Errorf("strip converter: invalid amount parameter (%w)", err)
}
sc.substr = ""
}
return sc, nil
}
// Convert trims the input string based on the StripConverter's configuration.
// It returns a CAST_FAILED error if the input is not a string.
func (sc StripConverter) Convert(in any) (any, error) {
str, ok := in.(string)
if !ok {
return nil, fmt.Errorf("strip converter: %w", ErrCastFailed)
}
if sc.amount <= 0 && sc.amount != -1 {
return str, nil
}
switch sc.side {
case MetaPrefix:
if sc.substr == EmptyString {
if sc.amount < len(str) {
return str[sc.amount:], nil
}
return EmptyString, nil
}
if sc.amount != -1 {
return strings.TrimPrefix(str, sc.substr), nil
}
return strings.TrimLeft(str, sc.substr), nil
case MetaSuffix:
if sc.substr == EmptyString {
if sc.amount < len(str) {
return str[:len(str)-sc.amount], nil
}
return EmptyString, nil
}
if sc.amount != -1 {
return strings.TrimSuffix(str, sc.substr), nil
}
return strings.TrimRight(str, sc.substr), nil
case MetaBoth:
if sc.substr == EmptyString {
if sc.amount*2 < len(str) {
return str[sc.amount : len(str)-sc.amount], nil
}
return EmptyString, nil
}
if sc.amount != -1 {
str = strings.TrimPrefix(str, sc.substr)
return strings.TrimSuffix(str, sc.substr), nil
}
return strings.Trim(str, sc.substr), nil
default:
return EmptyString, errors.New("strip converter: invalid side parameter")
}
}
// GigawordsConverter converts a value in Gigawords to octects
type GigawordsConverter struct{}
func (GigawordsConverter) Convert(in any) (any, error) {
gigawordsValue, err := IfaceAsInt64(in)
if err != nil {
return nil, err
}
totalOctects := (gigawordsValue * int64(math.Pow(2, 32))) // 2^32
return totalOctects, nil
}