diff --git a/.chloggen/ottl_append.yaml b/.chloggen/ottl_append.yaml new file mode 100644 index 000000000000..f6aac97e5ba8 --- /dev/null +++ b/.chloggen/ottl_append.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# 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: Introducing `append` function for appending items into an existing array + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [32141] + +# (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: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] \ No newline at end of file diff --git a/pkg/ottl/e2e/e2e_test.go b/pkg/ottl/e2e/e2e_test.go index e127787d26fe..7fb0a26732c9 100644 --- a/pkg/ottl/e2e/e2e_test.go +++ b/pkg/ottl/e2e/e2e_test.go @@ -237,6 +237,45 @@ func Test_e2e_editors(t *testing.T) { tCtx.GetLogRecord().Attributes().PutStr("total.string", "1") }, }, + { + statement: `append(attributes["foo"]["slice"], "sample_value")`, + want: func(tCtx ottllog.TransformContext) { + v, _ := tCtx.GetLogRecord().Attributes().Get("foo") + sv, _ := v.Map().Get("slice") + s := sv.Slice() + s.AppendEmpty().SetStr("sample_value") + + }, + }, + { + statement: `append(attributes["foo"]["flags"], "sample_value")`, + want: func(tCtx ottllog.TransformContext) { + v, _ := tCtx.GetLogRecord().Attributes().Get("foo") + s := v.Map().PutEmptySlice("flags") + s.AppendEmpty().SetStr("pass") + s.AppendEmpty().SetStr("sample_value") + + }, + }, + { + statement: `append(attributes["foo"]["slice"], values=[5,6])`, + want: func(tCtx ottllog.TransformContext) { + v, _ := tCtx.GetLogRecord().Attributes().Get("foo") + sv, _ := v.Map().Get("slice") + s := sv.Slice() + s.AppendEmpty().SetInt(5) + s.AppendEmpty().SetInt(6) + }, + }, + { + statement: `append(attributes["foo"]["new_slice"], values=[5,6])`, + want: func(tCtx ottllog.TransformContext) { + v, _ := tCtx.GetLogRecord().Attributes().Get("foo") + s := v.Map().PutEmptySlice("new_slice") + s.AppendEmpty().SetInt(5) + s.AppendEmpty().SetInt(6) + }, + }, } for _, tt := range tests { diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index 6fe2ae60858d..cf39931f489e 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -45,6 +45,7 @@ Editors: Available Editors: +- [append](#append) - [delete_key](#delete_key) - [delete_matching_keys](#delete_matching_keys) - [keep_matching_keys](#keep_matching_keys) @@ -59,6 +60,19 @@ Available Editors: - [set](#set) - [truncate_all](#truncate_all) +### append + +`append(target, Optional[value], Optional[values])` + +The `append` function appends single or multiple string values to `target`. +`append` converts scalar values into an array if the field exists but is not an array, and creates an array containing the provided values if the field doesn’t exist. + +Resulting field is always of type `pcommon.Slice` and will not convert the types of existing or new items in the slice. This means that it is possible to create a slice whose elements have different types. Be careful when using `append` to set attribute values, as this will produce values that are not possible to create through OpenTelemetry APIs [according to](https://opentelemetry.io/docs/specs/otel/common/#attribute) the OpenTelemetry specification. + + - `append(attributes["tags"], "prod")` + - `append(attributes["tags"], values = ["staging", "staging:east"])` + - `append(attributes["tags_copy"], attributes["tags"])` + ### delete_key `delete_key(target, key)` diff --git a/pkg/ottl/ottlfuncs/func_append.go b/pkg/ottl/ottlfuncs/func_append.go new file mode 100644 index 000000000000..362f89670375 --- /dev/null +++ b/pkg/ottl/ottlfuncs/func_append.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" + +import ( + "context" + "fmt" + + "go.opentelemetry.io/collector/pdata/pcommon" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" +) + +type AppendArguments[K any] struct { + Target ottl.GetSetter[K] + Value ottl.Optional[ottl.Getter[K]] + Values ottl.Optional[[]ottl.Getter[K]] +} + +func NewAppendFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("append", &AppendArguments[K]{}, createAppendFunction[K]) +} +func createAppendFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*AppendArguments[K]) + if !ok { + return nil, fmt.Errorf("AppendFactory args must be of type *Appendrguments[K]") + } + + return appendTo(args.Target, args.Value, args.Values) +} + +func appendTo[K any](target ottl.GetSetter[K], value ottl.Optional[ottl.Getter[K]], values ottl.Optional[[]ottl.Getter[K]]) (ottl.ExprFunc[K], error) { + if value.IsEmpty() && values.IsEmpty() { + return nil, fmt.Errorf("at least one of the optional arguments ('value' or 'values') must be provided") + } + + return func(ctx context.Context, tCtx K) (any, error) { + t, err := target.Get(ctx, tCtx) + if err != nil { + return nil, err + } + + // init res with target values + var res []any + + if t != nil { + switch targetType := t.(type) { + case pcommon.Slice: + res = append(res, targetType.AsRaw()...) + case pcommon.Value: + switch targetType.Type() { + case pcommon.ValueTypeEmpty: + res = append(res, targetType.Str()) + case pcommon.ValueTypeStr: + res = append(res, targetType.Str()) + case pcommon.ValueTypeInt: + res = append(res, targetType.Int()) + case pcommon.ValueTypeDouble: + res = append(res, targetType.Double()) + case pcommon.ValueTypeBool: + res = append(res, targetType.Bool()) + case pcommon.ValueTypeSlice: + res = append(res, targetType.Slice().AsRaw()...) + default: + return nil, fmt.Errorf("unsupported type of target field: %q", targetType.Type()) + } + + case []string: + res = appendMultiple(res, targetType) + case []any: + res = append(res, targetType...) + case []int64: + res = appendMultiple(res, targetType) + case []bool: + res = appendMultiple(res, targetType) + case []float64: + res = appendMultiple(res, targetType) + + case string: + res = append(res, targetType) + case int64: + res = append(res, targetType) + case bool: + res = append(res, targetType) + case float64: + res = append(res, targetType) + case any: + res = append(res, targetType) + default: + return nil, fmt.Errorf("unsupported type of target field: '%T'", t) + } + } + + appendGetterFn := func(g ottl.Getter[K]) error { + v, err := g.Get(ctx, tCtx) + if err != nil { + return err + } + res = append(res, v) + return nil + } + + if !value.IsEmpty() { + getter := value.Get() + if err := appendGetterFn(getter); err != nil { + return nil, err + } + } + if !values.IsEmpty() { + getters := values.Get() + for _, g := range getters { + if err := appendGetterFn(g); err != nil { + return nil, err + } + } + } + + // retype []any to Slice, having []any sometimes misbehaves and nils pcommon.Value + resSlice := pcommon.NewSlice() + if err := resSlice.FromRaw(res); err != nil { + return nil, err + } + + return nil, target.Set(ctx, tCtx, resSlice) + }, nil +} + +func appendMultiple[K any](target []any, values []K) []any { + for _, v := range values { + target = append(target, v) + } + return target +} diff --git a/pkg/ottl/ottlfuncs/func_append_test.go b/pkg/ottl/ottlfuncs/func_append_test.go new file mode 100644 index 000000000000..455dc37169de --- /dev/null +++ b/pkg/ottl/ottlfuncs/func_append_test.go @@ -0,0 +1,697 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottlfuncs + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" +) + +func Test_Append(t *testing.T) { + setter := func(_ context.Context, res any, val any) error { + rSlice := res.(pcommon.Slice) + vSlice := val.(pcommon.Slice) + assert.NoError(t, rSlice.FromRaw(vSlice.AsRaw())) + + return nil + } + + var nilOptional ottl.Optional[ottl.Getter[any]] + var nilSliceOptional ottl.Optional[[]ottl.Getter[any]] + + singleGetter := ottl.NewTestingOptional[ottl.Getter[any]](ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "a", nil + }, + }) + + singleIntGetter := ottl.NewTestingOptional[ottl.Getter[any]](ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return 66, nil + }, + }) + + multiGetter := ottl.NewTestingOptional[[]ottl.Getter[any]]( + []ottl.Getter[any]{ + ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "a", nil + }, + }, + ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "b", nil + }, + }, + }, + ) + + testCases := []struct { + Name string + Target ottl.GetSetter[any] + Value ottl.Optional[ottl.Getter[any]] + Values ottl.Optional[[]ottl.Getter[any]] + Want func(pcommon.Slice) + }{ + { + "Single: non existing target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return nil, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Single: non existing target - non string value", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return nil, nil + }, + Setter: setter, + }, + singleIntGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(66) + }, + }, + { + "Single: standard []string target - empty", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []string{}, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: standard []string target - empty", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []string{}, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: standard []string target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []any{"5", "6"}, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: standard []string target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []string{"5", "6"}, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: Slice target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + ps := pcommon.NewSlice() + if err := ps.FromRaw([]any{"5", "6"}); err != nil { + return nil, err + } + return ps, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: Slice target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + ps := pcommon.NewSlice() + if err := ps.FromRaw([]any{"5", "6"}); err != nil { + return nil, err + } + return ps, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: Slice target of string values in pcommon.value", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + ps := pcommon.NewSlice() + ps.AppendEmpty().SetStr("5") + ps.AppendEmpty().SetStr("6") + return ps, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: Slice target of string values in pcommon.value", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + ps := pcommon.NewSlice() + ps.AppendEmpty().SetStr("5") + ps.AppendEmpty().SetStr("6") + return ps, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: []any target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []any{5, 6}, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetInt(6) + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: []any target", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return []any{5, 6}, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetInt(6) + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: pcommon.Value - string", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + v := "5" + return v, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: pcommon.Value - string", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + v := "5" + return v, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: pcommon.Value - slice", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + v := pcommon.NewValueSlice() + if err := v.FromRaw([]any{"5", "6"}); err != nil { + return nil, err + } + return v, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: pcommon.Value - slice", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + v := pcommon.NewValueSlice() + if err := v.FromRaw([]any{"5", "6"}); err != nil { + return nil, err + } + return v, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("6") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: scalar target string", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "5", nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: scalar target string", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "5", nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("5") + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: scalar target any", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return 5, nil + }, + Setter: setter, + }, + singleGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetStr("a") + }, + }, + { + "Slice: scalar target any", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return 5, nil + }, + Setter: setter, + }, + nilOptional, + multiGetter, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + }, + }, + + { + "Single: scalar target any append int", + &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return 5, nil + }, + Setter: setter, + }, + singleIntGetter, + nilSliceOptional, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetInt(66) + }, + }, + } + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + exprFunc, err := appendTo[any](tc.Target, tc.Value, tc.Values) + assert.NoError(t, err) + + res := pcommon.NewSlice() + result, err := exprFunc(context.Background(), res) + assert.NoError(t, err) + assert.Nil(t, result) + assert.NotNil(t, res) + + expectedSlice := pcommon.NewSlice() + tc.Want(expectedSlice) + assert.EqualValues(t, expectedSlice, res) + }) + } +} + +func TestTargetType(t *testing.T) { + expectedInt := 5 + expectedSlice := pcommon.NewValueSlice() + assert.NoError(t, expectedSlice.Slice().FromRaw([]any{"a"})) + singleIntGetter := ottl.NewTestingOptional[ottl.Getter[any]](ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return expectedInt, nil + }, + }) + + testCases := []struct { + Name string + TargetValue any + Want func(pcommon.Slice) + expectedError bool + }{ + { + "pcommon.Slice", + expectedSlice.Slice(), + func(expectedValue pcommon.Slice) { + expectedSlice.Slice().MoveAndAppendTo(expectedValue) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeEmpty", + pcommon.NewValueEmpty(), + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("") + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeStr", + pcommon.NewValueStr("expected string"), + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("expected string") + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeInt", + pcommon.NewValueInt(4), + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(4) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeDouble", + pcommon.NewValueDouble(2.5), + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetDouble(2.5) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeBool", + pcommon.NewValueBool(true), + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetBool(true) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeSlice", + expectedSlice, + func(expectedValue pcommon.Slice) { + expectedSlice.Slice().MoveAndAppendTo(expectedValue) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "pcommon.ValueTypeMap", + pcommon.NewValueMap(), + func(_ pcommon.Slice) { + }, + true, + }, + + { + "string array", + []string{"a", "b"}, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "any array", + []any{"a", "b"}, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetStr("b") + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "int64 array", + []int64{5, 6}, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetInt(6) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "bool array", + []bool{false, true}, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetBool(false) + expectedValue.AppendEmpty().SetBool(true) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "float64 array", + []float64{1.5, 2.5}, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetDouble(1.5) + expectedValue.AppendEmpty().SetDouble(2.5) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + + { + "string", + "a", + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetStr("a") + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "int64 ", + 5, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetInt(5) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "bool", + true, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetBool(true) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + { + "float64 ", + 2.5, + func(expectedValue pcommon.Slice) { + expectedValue.AppendEmpty().SetDouble(2.5) + expectedValue.AppendEmpty().SetInt(int64(expectedInt)) + }, + false, + }, + } + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + target := &ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return tc.TargetValue, nil + }, + Setter: func(_ context.Context, res any, val any) error { + rSlice := res.(pcommon.Slice) + vSlice := val.(pcommon.Slice) + assert.NoError(t, rSlice.FromRaw(vSlice.AsRaw())) + + return nil + }, + } + + var nilSlice ottl.Optional[[]ottl.Getter[any]] + exprFunc, err := appendTo[any](target, singleIntGetter, nilSlice) + assert.NoError(t, err) + + res := pcommon.NewSlice() + result, err := exprFunc(context.Background(), res) + + if tc.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Nil(t, result) + assert.NotNil(t, res) + + expectedSlice := pcommon.NewSlice() + tc.Want(expectedSlice) + assert.EqualValues(t, expectedSlice, res) + } + }) + } +} + +func Test_ArgumentsArePresent(t *testing.T) { + var nilOptional ottl.Optional[ottl.Getter[any]] + var nilSliceOptional ottl.Optional[[]ottl.Getter[any]] + singleGetter := ottl.NewTestingOptional[ottl.Getter[any]](ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "val", nil + }, + }) + + multiGetter := ottl.NewTestingOptional[[]ottl.Getter[any]]( + []ottl.Getter[any]{ + ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "val1", nil + }, + }, + ottl.StandardGetSetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "val2", nil + }, + }, + }, + ) + testCases := []struct { + Name string + Value ottl.Optional[ottl.Getter[any]] + Values ottl.Optional[[]ottl.Getter[any]] + IsErrorExpected bool + }{ + {"providedBoth", singleGetter, multiGetter, false}, + {"provided values", nilOptional, multiGetter, false}, + {"provided value", singleGetter, nilSliceOptional, false}, + {"nothing provided", nilOptional, nilSliceOptional, true}, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + _, err := appendTo[any](nil, tc.Value, tc.Values) + assert.Equal(t, tc.IsErrorExpected, err != nil) + }) + } +} diff --git a/pkg/ottl/ottlfuncs/functions.go b/pkg/ottl/ottlfuncs/functions.go index 2a41a519231f..9f86b845ff2c 100644 --- a/pkg/ottl/ottlfuncs/functions.go +++ b/pkg/ottl/ottlfuncs/functions.go @@ -83,6 +83,7 @@ func converters[K any]() []ottl.Factory[K] { NewUnixNanoFactory[K](), NewUnixSecondsFactory[K](), NewUUIDFactory[K](), + NewAppendFactory[K](), NewYearFactory[K](), } }