diff --git a/.chloggen/ottl-use-stringgetter.yaml b/.chloggen/ottl-use-stringgetter.yaml new file mode 100755 index 000000000000..97dd020da185 --- /dev/null +++ b/.chloggen/ottl-use-stringgetter.yaml @@ -0,0 +1,17 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: breaking + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: pkg/ottl + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Updates `ConvertCase`, `ParseJSON`, `Split`, and `Substring` to use `StringGetter` + +# One or more tracking issues related to the change +issues: [19137] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + Affected components: `transformprocessor`, `filterprocessor`, `routingprocessor`. It is HIGHLY recommended to use each component's `error_mode` configuration option to handle errors returned by these functions. diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index e1666209de98..59b8bc320f1e 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -1,6 +1,16 @@ # OTTL Functions -The following functions are intended to be used in implementations of the OpenTelemetry Transformation Language that interact with otel data via the collector's internal data model, [pdata](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata). These functions may make assumptions about the types of the data returned by Paths. +The following functions are intended to be used in implementations of the OpenTelemetry Transformation Language that +interact with OTel data via the Collector's internal data model, [pdata](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata). +Functions generally expect specific types to be returned by `Paths`. +For these functions, if that type is not returned or if `nil` is returned, the function will error. +Some functions are able to handle different types and will generally convert those types to their desired type. +In these situations the function will error if it does not know how to do the conversion. +Use `ErrorMode` to determine how the `Statement` handles these errors. +See the component-specific guides for how each uses error mode: +- [filterprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor#ottl) +- [routingprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/routingprocessor#tech-preview-opentelemetry-transformation-language-statements-as-routing-conditions) +- [transformprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor#config) ## Functions @@ -38,7 +48,7 @@ List of available Converters: - [ConvertCase](#convertcase) - [Int](#int) - [IsMatch](#ismatch) -- [ParseJSON](#ParseJSON) +- [ParseJSON](#parsejson) - [SpanID](#spanid) - [Split](#split) - [TraceID](#traceid) @@ -72,7 +82,7 @@ The `ConvertCase` factory function converts the `target` string into the desired `target` is a string. `toCase` is a string. -If the `target` is not a string or does not exist, the `ConvertCase` factory function will return `nil`. +If the `target` is not a string or does not exist, the `ConvertCase` factory function will return an error. `toCase` can be: @@ -145,6 +155,7 @@ Examples: The `ParseJSON` factory function returns a `pcommon.Map` struct that is a result of parsing the target string as JSON `target` is a Getter that returns a string. This string should be in json format. +If `target` is not a string, nil, or cannot be parsed as JSON, `ParseJSON` will return an error. Unmarshalling is done using [jsoniter](https://github.com/json-iterator/go). Each JSON type is converted into a `pdata.Value` using the following map: @@ -188,7 +199,7 @@ The `Split` factory function separates a string by the delimiter, and returns an `target` is a string. `delimiter` is a string. -If the `target` is not a string or does not exist, the `Split` factory function will return `nil`. +If the `target` is not a string or does not exist, the `Split` factory function will return an error. Examples: @@ -214,7 +225,8 @@ The `Substring` Converter returns a substring from the given start index to the `target` is a string. `start` and `length` are `int64`. -The `Substring` Converter will return `nil` if the given parameters are invalid, e.x. `target` is not a string, or the start/length exceed the length of the `target` string. +If `target` is not a string or is nil, an error is returned. +If the start/length exceed the length of the `target` string, an error is returned. Examples: diff --git a/pkg/ottl/ottlfuncs/func_convert_case.go b/pkg/ottl/ottlfuncs/func_convert_case.go index 5cc79ac27835..25d48b8a72e7 100644 --- a/pkg/ottl/ottlfuncs/func_convert_case.go +++ b/pkg/ottl/ottlfuncs/func_convert_case.go @@ -24,46 +24,40 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func ConvertCase[K any](target ottl.Getter[K], toCase string) (ottl.ExprFunc[K], error) { +func ConvertCase[K any](target ottl.StringGetter[K], toCase string) (ottl.ExprFunc[K], error) { if toCase != "lower" && toCase != "upper" && toCase != "snake" && toCase != "camel" { return nil, fmt.Errorf("invalid case: %s, allowed cases are: lower, upper, snake, camel", toCase) } return func(ctx context.Context, tCtx K) (interface{}, error) { val, err := target.Get(ctx, tCtx) - if err != nil { return nil, err } - if valStr, ok := val.(string); ok { - - if valStr == "" { - return valStr, nil - } + if val == "" { + return val, nil + } - switch toCase { - // Convert string to lowercase (SOME_NAME -> some_name) - case "lower": - return strings.ToLower(valStr), nil + switch toCase { + // Convert string to lowercase (SOME_NAME -> some_name) + case "lower": + return strings.ToLower(val), nil - // Convert string to uppercase (some_name -> SOME_NAME) - case "upper": - return strings.ToUpper(valStr), nil + // Convert string to uppercase (some_name -> SOME_NAME) + case "upper": + return strings.ToUpper(val), nil - // Convert string to snake case (someName -> some_name) - case "snake": - return strcase.ToSnake(valStr), nil + // Convert string to snake case (someName -> some_name) + case "snake": + return strcase.ToSnake(val), nil - // Convert string to camel case (some_name -> SomeName) - case "camel": - return strcase.ToCamel(valStr), nil + // Convert string to camel case (some_name -> SomeName) + case "camel": + return strcase.ToCamel(val), nil - default: - return nil, fmt.Errorf("error handling unexpected case: %s", toCase) - } + default: + return nil, fmt.Errorf("error handling unexpected case: %s", toCase) } - - return nil, nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_convert_case_test.go b/pkg/ottl/ottlfuncs/func_convert_case_test.go index 18c4beb0d2a3..74d277709d1f 100644 --- a/pkg/ottl/ottlfuncs/func_convert_case_test.go +++ b/pkg/ottl/ottlfuncs/func_convert_case_test.go @@ -27,14 +27,14 @@ import ( func Test_convertCase(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] toCase string expected interface{} }{ // snake case { name: "snake simple convert", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simpleString", nil }, @@ -44,7 +44,7 @@ func Test_convertCase(t *testing.T) { }, { name: "snake noop already snake case", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simple_string", nil }, @@ -54,7 +54,7 @@ func Test_convertCase(t *testing.T) { }, { name: "snake multiple uppercase", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "CPUUtilizationMetric", nil }, @@ -64,7 +64,7 @@ func Test_convertCase(t *testing.T) { }, { name: "snake hyphens", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simple-string", nil }, @@ -72,19 +72,9 @@ func Test_convertCase(t *testing.T) { toCase: "snake", expected: "simple_string", }, - { - name: "snake nil", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return nil, nil - }, - }, - toCase: "snake", - expected: nil, - }, { name: "snake empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -95,7 +85,7 @@ func Test_convertCase(t *testing.T) { // camel case { name: "camel simple convert", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simple_string", nil }, @@ -105,7 +95,7 @@ func Test_convertCase(t *testing.T) { }, { name: "snake noop already snake case", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "SimpleString", nil }, @@ -115,7 +105,7 @@ func Test_convertCase(t *testing.T) { }, { name: "snake hyphens", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simple-string", nil }, @@ -123,19 +113,9 @@ func Test_convertCase(t *testing.T) { toCase: "camel", expected: "SimpleString", }, - { - name: "snake nil", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return nil, nil - }, - }, - toCase: "camel", - expected: nil, - }, { name: "snake empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -146,7 +126,7 @@ func Test_convertCase(t *testing.T) { // upper case { name: "upper simple", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simple", nil }, @@ -156,7 +136,7 @@ func Test_convertCase(t *testing.T) { }, { name: "upper complex", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "complex_SET-of.WORDS1234", nil }, @@ -166,7 +146,7 @@ func Test_convertCase(t *testing.T) { }, { name: "upper empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -177,7 +157,7 @@ func Test_convertCase(t *testing.T) { // lower case { name: "lower simple", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "SIMPLE", nil }, @@ -187,7 +167,7 @@ func Test_convertCase(t *testing.T) { }, { name: "lower complex", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "complex_SET-of.WORDS1234", nil }, @@ -197,7 +177,7 @@ func Test_convertCase(t *testing.T) { }, { name: "lower empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -205,28 +185,6 @@ func Test_convertCase(t *testing.T) { toCase: "lower", expected: "", }, - // nil test - { - name: "nil", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return nil, nil - }, - }, - toCase: "upper", - expected: nil, - }, - // non-string test - { - name: "non-string", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return 10, nil - }, - }, - toCase: "upper", - expected: nil, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -242,12 +200,12 @@ func Test_convertCase(t *testing.T) { func Test_convertCaseError(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] toCase string }{ { name: "error bad case", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "simpleString", nil }, @@ -263,3 +221,41 @@ func Test_convertCaseError(t *testing.T) { }) } } + +func Test_convertCaseRuntimeError(t *testing.T) { + tests := []struct { + name string + target ottl.StringGetter[interface{}] + toCase string + expectedError string + }{ + { + name: "non-string", + target: &ottl.StandardTypeGetter[interface{}, string]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 10, nil + }, + }, + toCase: "upper", + expectedError: "expected string but got int", + }, + { + name: "nil", + target: &ottl.StandardTypeGetter[interface{}, string]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return nil, nil + }, + }, + toCase: "snake", + expectedError: "expected string but got nil", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + exprFunc, err := ConvertCase[any](tt.target, tt.toCase) + require.NoError(t, err) + _, err = exprFunc(context.Background(), nil) + assert.ErrorContains(t, err, tt.expectedError) + }) + } +} diff --git a/pkg/ottl/ottlfuncs/func_parse_json.go b/pkg/ottl/ottlfuncs/func_parse_json.go index bfb03506fc5a..13066bfd605e 100644 --- a/pkg/ottl/ottlfuncs/func_parse_json.go +++ b/pkg/ottl/ottlfuncs/func_parse_json.go @@ -16,7 +16,6 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" - "fmt" jsoniter "github.com/json-iterator/go" "go.opentelemetry.io/collector/pdata/pcommon" @@ -33,18 +32,14 @@ import ( // JSON null -> nil // JSON arrays -> pdata.SliceValue // JSON objects -> map[string]any -func ParseJSON[K any](target ottl.Getter[K]) (ottl.ExprFunc[K], error) { +func ParseJSON[K any](target ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { return func(ctx context.Context, tCtx K) (interface{}, error) { targetVal, err := target.Get(ctx, tCtx) if err != nil { return nil, err } - jsonStr, ok := targetVal.(string) - if !ok { - return nil, fmt.Errorf("target must be a string but got %T", targetVal) - } var parsedValue map[string]interface{} - err = jsoniter.UnmarshalFromString(jsonStr, &parsedValue) + err = jsoniter.UnmarshalFromString(targetVal, &parsedValue) if err != nil { return nil, err } diff --git a/pkg/ottl/ottlfuncs/func_parse_json_test.go b/pkg/ottl/ottlfuncs/func_parse_json_test.go index 2ea582b213c7..e64cc32df2b7 100644 --- a/pkg/ottl/ottlfuncs/func_parse_json_test.go +++ b/pkg/ottl/ottlfuncs/func_parse_json_test.go @@ -28,12 +28,12 @@ import ( func Test_ParseJSON(t *testing.T) { tests := []struct { name string - target ottl.Getter[any] + target ottl.StringGetter[any] want func(pcommon.Map) }{ { name: "handle string", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":"string value"}`, nil }, @@ -44,7 +44,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle bool", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":true}`, nil }, @@ -55,7 +55,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle int", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":1}`, nil }, @@ -66,7 +66,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle float", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":1.1}`, nil }, @@ -77,7 +77,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle nil", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":null}`, nil }, @@ -88,7 +88,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle array", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":["string","value"]}`, nil }, @@ -101,7 +101,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "handle nested object", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test":{"nested":"true"}}`, nil }, @@ -113,7 +113,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "updates existing", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"existing":"pass"}`, nil }, @@ -124,7 +124,7 @@ func Test_ParseJSON(t *testing.T) { }, { name: "complex", - target: ottl.StandardGetSetter[any]{ + target: ottl.StandardTypeGetter[any, string]{ Getter: func(ctx context.Context, tCtx any) (interface{}, error) { return `{"test1":{"nested":"true"},"test2":"string","test3":1,"test4":1.1,"test5":[[1], [2, 3],[]],"test6":null}`, nil }, @@ -172,7 +172,7 @@ func Test_ParseJSON(t *testing.T) { } func Test_ParseJSON_Error(t *testing.T) { - target := &ottl.StandardGetSetter[interface{}]{ + target := &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return 1, nil }, diff --git a/pkg/ottl/ottlfuncs/func_split.go b/pkg/ottl/ottlfuncs/func_split.go index fee994288d9d..5f370c7354d4 100644 --- a/pkg/ottl/ottlfuncs/func_split.go +++ b/pkg/ottl/ottlfuncs/func_split.go @@ -21,17 +21,12 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Split[K any](target ottl.Getter[K], delimiter string) (ottl.ExprFunc[K], error) { +func Split[K any](target ottl.StringGetter[K], delimiter string) (ottl.ExprFunc[K], error) { return func(ctx context.Context, tCtx K) (interface{}, error) { val, err := target.Get(ctx, tCtx) if err != nil { return nil, err } - if val != nil { - if valStr, ok := val.(string); ok { - return strings.Split(valStr, delimiter), nil - } - } - return nil, nil + return strings.Split(val, delimiter), nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_split_test.go b/pkg/ottl/ottlfuncs/func_split_test.go index 26abbe9ebf1f..7ad8d3e32615 100644 --- a/pkg/ottl/ottlfuncs/func_split_test.go +++ b/pkg/ottl/ottlfuncs/func_split_test.go @@ -26,13 +26,13 @@ import ( func Test_split(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] delimiter string expected interface{} }{ { name: "split string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "A|B|C", nil }, @@ -42,7 +42,7 @@ func Test_split(t *testing.T) { }, { name: "split empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -52,7 +52,7 @@ func Test_split(t *testing.T) { }, { name: "split empty delimiter", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "A|B|C", nil }, @@ -62,7 +62,7 @@ func Test_split(t *testing.T) { }, { name: "split empty string and empty delimiter", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -70,16 +70,6 @@ func Test_split(t *testing.T) { delimiter: "", expected: []string{}, }, - { - name: "split non-string", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return 123, nil - }, - }, - delimiter: "|", - expected: nil, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -91,3 +81,15 @@ func Test_split(t *testing.T) { }) } } + +func Test_Split_Error(t *testing.T) { + target := &ottl.StandardTypeGetter[interface{}, string]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1, nil + }, + } + exprFunc, err := Split[interface{}](target, ",") + assert.NoError(t, err) + _, err = exprFunc(context.Background(), nil) + assert.Error(t, err) +} diff --git a/pkg/ottl/ottlfuncs/func_substring.go b/pkg/ottl/ottlfuncs/func_substring.go index 5bdaf81cbc3d..10cdee594124 100644 --- a/pkg/ottl/ottlfuncs/func_substring.go +++ b/pkg/ottl/ottlfuncs/func_substring.go @@ -21,7 +21,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Substring[K any](target ottl.Getter[K], start int64, length int64) (ottl.ExprFunc[K], error) { +func Substring[K any](target ottl.StringGetter[K], start int64, length int64) (ottl.ExprFunc[K], error) { if start < 0 { return nil, fmt.Errorf("invalid start for substring function, %d cannot be negative", start) } @@ -34,12 +34,9 @@ func Substring[K any](target ottl.Getter[K], start int64, length int64) (ottl.Ex if err != nil { return nil, err } - if valStr, ok := val.(string); ok { - if (start + length) > int64(len(valStr)) { - return nil, fmt.Errorf("invalid range for substring function, %d cannot be greater than the length of target string(%d)", start+length, len(valStr)) - } - return valStr[start : start+length], nil + if (start + length) > int64(len(val)) { + return nil, fmt.Errorf("invalid range for substring function, %d cannot be greater than the length of target string(%d)", start+length, len(val)) } - return nil, nil + return val[start : start+length], nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_substring_test.go b/pkg/ottl/ottlfuncs/func_substring_test.go index 20845bd714dd..389479709d71 100644 --- a/pkg/ottl/ottlfuncs/func_substring_test.go +++ b/pkg/ottl/ottlfuncs/func_substring_test.go @@ -26,14 +26,14 @@ import ( func Test_substring(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] start int64 length int64 expected interface{} }{ { name: "substring", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "123456789", nil }, @@ -44,7 +44,7 @@ func Test_substring(t *testing.T) { }, { name: "substring with result of total string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "123456789", nil }, @@ -53,26 +53,6 @@ func Test_substring(t *testing.T) { length: 9, expected: "123456789", }, - { - name: "substring non-string", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return 123456789, nil - }, - }, - start: 3, - length: 6, - }, - { - name: "substring nil string", - target: &ottl.StandardGetSetter[interface{}]{ - Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return nil, nil - }, - }, - start: 3, - length: 6, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -88,13 +68,13 @@ func Test_substring(t *testing.T) { func Test_substring_validation(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] start int64 length int64 }{ { name: "substring with result of empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "123456789", nil }, @@ -104,7 +84,7 @@ func Test_substring_validation(t *testing.T) { }, { name: "substring with invalid start index", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "123456789", nil }, @@ -124,13 +104,13 @@ func Test_substring_validation(t *testing.T) { func Test_substring_error(t *testing.T) { tests := []struct { name string - target ottl.Getter[interface{}] + target ottl.StringGetter[interface{}] start int64 length int64 }{ { name: "substring empty string", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "", nil }, @@ -140,7 +120,7 @@ func Test_substring_error(t *testing.T) { }, { name: "substring with invalid length index", - target: &ottl.StandardGetSetter[interface{}]{ + target: &ottl.StandardTypeGetter[interface{}, string]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { return "123456789", nil }, @@ -148,6 +128,26 @@ func Test_substring_error(t *testing.T) { start: 3, length: 20, }, + { + name: "substring non-string", + target: &ottl.StandardTypeGetter[interface{}, string]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 123456789, nil + }, + }, + start: 3, + length: 6, + }, + { + name: "substring nil string", + target: &ottl.StandardTypeGetter[interface{}, string]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return nil, nil + }, + }, + start: 3, + length: 6, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {