diff --git a/utils/consts.go b/utils/consts.go index ebfb03ce7..328fd8019 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -695,6 +695,7 @@ const ( VersionName = "Version" MetaTenant = "*tenant" ResourceUsage = "ResourceUsage" + MetaStrip = "*strip" MetaDuration = "*duration" MetaLibPhoneNumber = "*libphonenumber" MetaTimeString = "*time_string" diff --git a/utils/dataconverter.go b/utils/dataconverter.go index 251c93366..555adab7d 100644 --- a/utils/dataconverter.go +++ b/utils/dataconverter.go @@ -117,6 +117,8 @@ func NewDataConverter(params string) (conv DataConverter, err error) { return NewRandomConverter(EmptyString) } return NewRandomConverter(params[len(MetaRandom)+1:]) + case strings.HasPrefix(params, MetaStrip): + return NewStripConverter(params) default: return nil, fmt.Errorf("unsupported converter definition: <%s>", params) } @@ -586,3 +588,84 @@ func (jsnC JSONConverter) Convert(in any) (any, error) { } return string(b), nil } + +// StripConverter strips characters from the left or the right side of a string. +type StripConverter struct { + side string + substr string + amount int +} + +// NewStripConverter creates a new StripConverter with the specified parameters. +// The input params string should have the format ":::", where: +// - 'type' must be "*strip". +// - 'side' specifies the side of the string to strip, and it must be either "*left" or "*right". +// - 'substr' is the substring to be removed from the string. +// - 'amount' is the optional number of times 'substr' should be removed from the specified side. +// +// If 'amount' is omitted, it defaults to 1. If it is -1, all instances of 'substr' will be removed from the +// chosen side. If 'substr' is "*any", 'amount' characters will be removed from the specified side +// regardless of their value. +func NewStripConverter(params string) (DataConverter, error) { + sc := new(StripConverter) + paramSlice := strings.Split(params, InInFieldSep) + switch len(paramSlice) { + case 3: + sc.amount = 1 + case 4: + var err error + sc.amount, err = strconv.Atoi(paramSlice[3]) + if err != nil { + return nil, err + } + default: + return nil, errors.New("strip converter: invalid number of parameters (should have 3 or 4)") + } + + if sc.side = paramSlice[1]; !IsSliceMember([]string{MetaLeft, MetaRight}, sc.side) { + return nil, errors.New("strip converter: invalid side parameter") + } + + sc.substr = paramSlice[2] + if sc.substr == MetaAny && sc.amount == -1 { + return nil, errors.New("strip converter: invalid combination of substr and amount values") + } + if sc.substr != MetaAny && sc.amount != -1 { + sc.substr = strings.Repeat(paramSlice[2], sc.amount) + } + 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, ErrCastFailed + } + switch sc.side { + case MetaLeft: + if sc.substr == MetaAny { + if sc.amount < len(str) { + return str[sc.amount:], nil + } + return str, nil + } + if sc.amount != -1 { + return strings.TrimPrefix(str, sc.substr), nil + } + return strings.TrimLeft(str, sc.substr), nil + case MetaRight: + if sc.substr == MetaAny { + if sc.amount < len(str) { + return str[:len(str)-sc.amount], nil + } + return str, nil + } + if sc.amount != -1 { + return strings.TrimSuffix(str, sc.substr), nil + } + return strings.TrimRight(str, sc.substr), nil + } + return str, nil +} diff --git a/utils/dataconverter_test.go b/utils/dataconverter_test.go index 540385d02..d89a65227 100644 --- a/utils/dataconverter_test.go +++ b/utils/dataconverter_test.go @@ -1285,3 +1285,105 @@ func TestDataConverterConvertJSONOK(t *testing.T) { t.Errorf("expected: <%+v>, \nreceived: <%+v>", exp, rcv) } } + +func TestStripConverter(t *testing.T) { + tests := []struct { + name string + params string + input string + expected string + expectErr bool + }{ + { + name: "Cut any leading 3 characters", + params: "*strip:*left:*any:3", + input: "000000435400", + expected: "000435400", + expectErr: false, + }, + { + name: "Cut leading '00' twice", + params: "*strip:*left:00:2", + input: "000000435400", + expected: "00435400", + expectErr: false, + }, + { + name: "Cut leading '00' default", + params: "*strip:*left:00", + input: "000000435400", + expected: "0000435400", + expectErr: false, + }, + { + name: "Cut 8 trailing nils", + params: "*strip:*right:\u0000:8", + input: "password\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", + expected: "password", + expectErr: false, + }, + { + name: "Cut 8 trailing escaped nils", + params: "*strip:*right:\\u0000:8", + input: "password\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000", + expected: "password", + expectErr: false, + }, + { + name: "Cut all trailing nils", + params: "*strip:*right:\u0000:-1", + input: "password\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", + expected: "password", + expectErr: false, + }, + { + name: "Cut all escaped trailing nils", + params: "*strip:*right:\\u0000:-1", + input: "password\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000", + expected: "password", + expectErr: false, + }, + { + name: "Invalid substr and amount value combination", + params: "*strip:*right:*any:-1", + input: "test", + expected: "", + expectErr: true, + }, + { + name: "Invalid nr of parameters", + params: "*strip:*right", + input: "test", + expected: "", + expectErr: true, + }, + { + name: "Invalid side parameter", + params: "*strip:*up:abc:2", + input: "test", + expected: "", + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sc, err := NewDataConverter(tt.params) + if (err != nil) != tt.expectErr { + t.Errorf("NewStripConverter error = %v, expectErr %v", err, tt.expectErr) + return + } + if tt.expectErr { + return + } + rcv, err := sc.Convert(tt.input) + if (err != nil) != tt.expectErr { + t.Errorf("Convert error = %v, expectErr %v", err, tt.expectErr) + return + } + if rcv != tt.expected { + t.Errorf("expected: %s, received: %s", tt.expected, rcv) + } + }) + } +}