From b2290e67db8eda8bef46e96d561b1adb58a23adf Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 2 May 2023 21:18:09 -0400 Subject: [PATCH] [pkg/ottl] Define functions using a Factory interface (#18822) Convert OTTL factories to be defined with an interface Co-authored-by: Evan Bradley --- .../ottl-function-factory-interface.yaml | 18 + internal/filter/filterottl/filter.go | 79 +-- pkg/ottl/boolean_value_test.go | 22 +- pkg/ottl/contexts/ottldatapoint/datapoint.go | 2 +- pkg/ottl/contexts/ottllog/log.go | 2 +- pkg/ottl/contexts/ottlmetric/metrics.go | 2 +- pkg/ottl/contexts/ottlresource/resource.go | 2 +- pkg/ottl/contexts/ottlscope/scope.go | 2 +- pkg/ottl/contexts/ottlspan/span.go | 2 +- .../contexts/ottlspanevent/span_events.go | 2 +- pkg/ottl/expression_test.go | 6 +- pkg/ottl/factory.go | 96 +++ pkg/ottl/functions.go | 97 ++-- pkg/ottl/functions_test.go | 547 +++++++++++++----- pkg/ottl/math_test.go | 28 +- pkg/ottl/ottlfuncs/func_concat.go | 23 +- pkg/ottl/ottlfuncs/func_concat_test.go | 8 +- pkg/ottl/ottlfuncs/func_convert_case.go | 21 +- pkg/ottl/ottlfuncs/func_convert_case_test.go | 6 +- pkg/ottl/ottlfuncs/func_delete_key.go | 24 +- pkg/ottl/ottlfuncs/func_delete_key_test.go | 15 +- .../ottlfuncs/func_delete_matching_keys.go | 21 +- .../func_delete_matching_keys_test.go | 8 +- pkg/ottl/ottlfuncs/func_int.go | 23 +- pkg/ottl/ottlfuncs/func_int_test.go | 3 +- pkg/ottl/ottlfuncs/func_is_match.go | 21 +- pkg/ottl/ottlfuncs/func_is_match_test.go | 6 +- pkg/ottl/ottlfuncs/func_keep_keys.go | 24 +- pkg/ottl/ottlfuncs/func_keep_keys_test.go | 15 +- pkg/ottl/ottlfuncs/func_limit.go | 22 +- pkg/ottl/ottlfuncs/func_limit_test.go | 8 +- pkg/ottl/ottlfuncs/func_merge_maps.go | 24 +- pkg/ottl/ottlfuncs/func_merge_maps_test.go | 6 +- pkg/ottl/ottlfuncs/func_parse_json.go | 25 +- pkg/ottl/ottlfuncs/func_parse_json_test.go | 9 +- .../ottlfuncs/func_replace_all_matches.go | 22 +- .../func_replace_all_matches_test.go | 6 +- .../ottlfuncs/func_replace_all_patterns.go | 23 +- .../func_replace_all_patterns_test.go | 10 +- pkg/ottl/ottlfuncs/func_replace_match.go | 22 +- pkg/ottl/ottlfuncs/func_replace_match_test.go | 6 +- pkg/ottl/ottlfuncs/func_replace_pattern.go | 22 +- .../ottlfuncs/func_replace_pattern_test.go | 8 +- pkg/ottl/ottlfuncs/func_set.go | 24 +- pkg/ottl/ottlfuncs/func_set_test.go | 6 +- pkg/ottl/ottlfuncs/func_span_id.go | 21 +- pkg/ottl/ottlfuncs/func_span_id_test.go | 4 +- pkg/ottl/ottlfuncs/func_split.go | 24 +- pkg/ottl/ottlfuncs/func_split_test.go | 8 +- pkg/ottl/ottlfuncs/func_substring.go | 22 +- pkg/ottl/ottlfuncs/func_substring_test.go | 6 +- pkg/ottl/ottlfuncs/func_trace_id.go | 21 +- pkg/ottl/ottlfuncs/func_trace_id_test.go | 4 +- pkg/ottl/ottlfuncs/func_truncate_all.go | 19 + pkg/ottl/parser.go | 4 +- .../internal/common/functions.go | 46 +- .../internal/common/functions.go | 26 +- .../internal/common/functions.go | 51 +- .../internal/common/logs.go | 2 +- .../internal/common/metrics.go | 4 +- .../internal/common/traces.go | 4 +- .../internal/logs/functions.go | 3 +- .../metrics/func_convert_gauge_to_sum.go | 19 + .../metrics/func_convert_sum_to_gauge.go | 8 + .../func_convert_summary_count_val_to_sum.go | 19 + .../func_convert_summary_sum_val_to_sum.go | 19 + .../internal/metrics/functions.go | 29 +- .../internal/metrics/functions_test.go | 8 +- .../internal/traces/functions.go | 5 +- 69 files changed, 1320 insertions(+), 424 deletions(-) create mode 100755 .chloggen/ottl-function-factory-interface.yaml create mode 100644 pkg/ottl/factory.go diff --git a/.chloggen/ottl-function-factory-interface.yaml b/.chloggen/ottl-function-factory-interface.yaml new file mode 100755 index 000000000000..c8b2e5be501d --- /dev/null +++ b/.chloggen/ottl-function-factory-interface.yaml @@ -0,0 +1,18 @@ +# 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: Reimplement all OTTL function factories to implement the `ottl.Factory` interface. + +# One or more tracking issues related to the change +issues: [14712] + +# (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: | + The `ottl.Factory` interface allows making factories extendable and defines + canonical names for the functions across components using the OTTL. \ No newline at end of file diff --git a/internal/filter/filterottl/filter.go b/internal/filter/filterottl/filter.go index 7f30dca0ddd8..0516831405d4 100644 --- a/internal/filter/filterottl/filter.go +++ b/internal/filter/filterottl/filter.go @@ -32,9 +32,10 @@ import ( // NewBoolExprForSpan creates a BoolExpr[ottlspan.TransformContext] that will return true if any of the given OTTL conditions evaluate to true. // The passed in functions should use the ottlspan.TransformContext. // If a function named `drop` is not present in the function map it will be added automatically so that parsing works as expected -func NewBoolExprForSpan(conditions []string, functions map[string]interface{}, errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlspan.TransformContext], error) { - if _, ok := functions["drop"]; !ok { - functions["drop"] = drop[ottlspan.TransformContext] +func NewBoolExprForSpan(conditions []string, functions map[string]ottl.Factory[ottlspan.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlspan.TransformContext], error) { + drop := newDropFactory[ottlspan.TransformContext]() + if _, ok := functions[drop.Name()]; !ok { + functions[drop.Name()] = drop } statmentsStr := conditionsToStatements(conditions) parser, err := ottlspan.NewParser(functions, set) @@ -52,9 +53,10 @@ func NewBoolExprForSpan(conditions []string, functions map[string]interface{}, e // NewBoolExprForSpanEvent creates a BoolExpr[ottlspanevent.TransformContext] that will return true if any of the given OTTL conditions evaluate to true. // The passed in functions should use the ottlspanevent.TransformContext. // If a function named `drop` is not present in the function map it will be added automatically so that parsing works as expected -func NewBoolExprForSpanEvent(conditions []string, functions map[string]interface{}, errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlspanevent.TransformContext], error) { - if _, ok := functions["drop"]; !ok { - functions["drop"] = drop[ottlspanevent.TransformContext] +func NewBoolExprForSpanEvent(conditions []string, functions map[string]ottl.Factory[ottlspanevent.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlspanevent.TransformContext], error) { + drop := newDropFactory[ottlspanevent.TransformContext]() + if _, ok := functions[drop.Name()]; !ok { + functions[drop.Name()] = drop } statmentsStr := conditionsToStatements(conditions) parser, err := ottlspanevent.NewParser(functions, set) @@ -72,9 +74,10 @@ func NewBoolExprForSpanEvent(conditions []string, functions map[string]interface // NewBoolExprForMetric creates a BoolExpr[ottlmetric.TransformContext] that will return true if any of the given OTTL conditions evaluate to true. // The passed in functions should use the ottlmetric.TransformContext. // If a function named `drop` is not present in the function map it will be added automatically so that parsing works as expected -func NewBoolExprForMetric(conditions []string, functions map[string]interface{}, errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlmetric.TransformContext], error) { - if _, ok := functions["drop"]; !ok { - functions["drop"] = drop[ottlmetric.TransformContext] +func NewBoolExprForMetric(conditions []string, functions map[string]ottl.Factory[ottlmetric.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottlmetric.TransformContext], error) { + drop := newDropFactory[ottlmetric.TransformContext]() + if _, ok := functions[drop.Name()]; !ok { + functions[drop.Name()] = drop } statmentsStr := conditionsToStatements(conditions) parser, err := ottlmetric.NewParser(functions, set) @@ -92,9 +95,10 @@ func NewBoolExprForMetric(conditions []string, functions map[string]interface{}, // NewBoolExprForDataPoint creates a BoolExpr[ottldatapoint.TransformContext] that will return true if any of the given OTTL conditions evaluate to true. // The passed in functions should use the ottldatapoint.TransformContext. // If a function named `drop` is not present in the function map it will be added automatically so that parsing works as expected -func NewBoolExprForDataPoint(conditions []string, functions map[string]interface{}, errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottldatapoint.TransformContext], error) { - if _, ok := functions["drop"]; !ok { - functions["drop"] = drop[ottldatapoint.TransformContext] +func NewBoolExprForDataPoint(conditions []string, functions map[string]ottl.Factory[ottldatapoint.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottldatapoint.TransformContext], error) { + drop := newDropFactory[ottldatapoint.TransformContext]() + if _, ok := functions[drop.Name()]; !ok { + functions[drop.Name()] = drop } statmentsStr := conditionsToStatements(conditions) parser, err := ottldatapoint.NewParser(functions, set) @@ -112,9 +116,10 @@ func NewBoolExprForDataPoint(conditions []string, functions map[string]interface // NewBoolExprForLog creates a BoolExpr[ottllog.TransformContext] that will return true if any of the given OTTL conditions evaluate to true. // The passed in functions should use the ottllog.TransformContext. // If a function named `drop` is not present in the function map it will be added automatically so that parsing works as expected -func NewBoolExprForLog(conditions []string, functions map[string]interface{}, errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottllog.TransformContext], error) { - if _, ok := functions["drop"]; !ok { - functions["drop"] = drop[ottllog.TransformContext] +func NewBoolExprForLog(conditions []string, functions map[string]ottl.Factory[ottllog.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (expr.BoolExpr[ottllog.TransformContext], error) { + drop := newDropFactory[ottllog.TransformContext]() + if _, ok := functions[drop.Name()]; !ok { + functions[drop.Name()] = drop } statmentsStr := conditionsToStatements(conditions) parser, err := ottllog.NewParser(functions, set) @@ -137,41 +142,49 @@ func conditionsToStatements(conditions []string) []string { return statements } -func StandardSpanFuncs() map[string]interface{} { +func StandardSpanFuncs() map[string]ottl.Factory[ottlspan.TransformContext] { return standardFuncs[ottlspan.TransformContext]() } -func StandardSpanEventFuncs() map[string]interface{} { +func StandardSpanEventFuncs() map[string]ottl.Factory[ottlspanevent.TransformContext] { return standardFuncs[ottlspanevent.TransformContext]() } -func StandardMetricFuncs() map[string]interface{} { +func StandardMetricFuncs() map[string]ottl.Factory[ottlmetric.TransformContext] { return standardFuncs[ottlmetric.TransformContext]() } -func StandardDataPointFuncs() map[string]interface{} { +func StandardDataPointFuncs() map[string]ottl.Factory[ottldatapoint.TransformContext] { return standardFuncs[ottldatapoint.TransformContext]() } -func StandardLogFuncs() map[string]interface{} { +func StandardLogFuncs() map[string]ottl.Factory[ottllog.TransformContext] { return standardFuncs[ottllog.TransformContext]() } -func standardFuncs[K any]() map[string]interface{} { - return map[string]interface{}{ - "TraceID": ottlfuncs.TraceID[K], - "SpanID": ottlfuncs.SpanID[K], - "IsMatch": ottlfuncs.IsMatch[K], - "Concat": ottlfuncs.Concat[K], - "Split": ottlfuncs.Split[K], - "Int": ottlfuncs.Int[K], - "ConvertCase": ottlfuncs.ConvertCase[K], - "Substring": ottlfuncs.Substring[K], - "drop": drop[K], - } +func standardFuncs[K any]() map[string]ottl.Factory[K] { + return ottl.CreateFactoryMap( + ottlfuncs.NewTraceIDFactory[K](), + ottlfuncs.NewSpanIDFactory[K](), + ottlfuncs.NewIsMatchFactory[K](), + ottlfuncs.NewConcatFactory[K](), + ottlfuncs.NewSplitFactory[K](), + ottlfuncs.NewIntFactory[K](), + ottlfuncs.NewConvertCaseFactory[K](), + ottlfuncs.NewSubstringFactory[K](), + newDropFactory[K](), + ) +} + +func newDropFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("drop", nil, createDropFunction[K]) +} + +func createDropFunction[K any](_ ottl.FunctionContext, _ ottl.Arguments) (ottl.ExprFunc[K], error) { + return dropFn[K]() } -func drop[K any]() (ottl.ExprFunc[K], error) { +func dropFn[K any]() (ottl.ExprFunc[K], error) { return func(context.Context, K) (interface{}, error) { return true, nil }, nil diff --git a/pkg/ottl/boolean_value_test.go b/pkg/ottl/boolean_value_test.go index 7e5648d86cdd..dcb4e1e53ca6 100644 --- a/pkg/ottl/boolean_value_test.go +++ b/pkg/ottl/boolean_value_test.go @@ -80,7 +80,7 @@ func comparisonHelper(left any, right any, op string) *comparison { } func Test_newComparisonEvaluator(t *testing.T) { - p, _ := NewParser[any]( + p, _ := NewParser( defaultFunctionsForTests(), testParsePath, componenttest.NewNopTelemetrySettings(), @@ -131,7 +131,7 @@ func Test_newComparisonEvaluator(t *testing.T) { } func Test_newConditionEvaluator_invalid(t *testing.T) { - p, _ := NewParser[any]( + p, _ := NewParser( defaultFunctionsForTests(), testParsePath, componenttest.NewNopTelemetrySettings(), @@ -163,23 +163,23 @@ func Test_newConditionEvaluator_invalid(t *testing.T) { } } -func True[K any]() (ExprFunc[K], error) { - return func(ctx context.Context, tCtx K) (interface{}, error) { +func True() (ExprFunc[any], error) { + return func(ctx context.Context, tCtx any) (interface{}, error) { return true, nil }, nil } -func False[K any]() (ExprFunc[K], error) { - return func(ctx context.Context, tCtx K) (interface{}, error) { +func False() (ExprFunc[any], error) { + return func(ctx context.Context, tCtx any) (interface{}, error) { return false, nil }, nil } func Test_newBooleanExpressionEvaluator(t *testing.T) { functions := defaultFunctionsForTests() - functions["True"] = True[any] - functions["False"] = False[any] + functions["True"] = createFactory("True", &struct{}{}, True) + functions["False"] = createFactory("False", &struct{}{}, False) - p, _ := NewParser[any]( + p, _ := NewParser( functions, testParsePath, componenttest.NewNopTelemetrySettings(), @@ -560,9 +560,9 @@ func Test_newBooleanExpressionEvaluator(t *testing.T) { } func Test_newBooleanExpressionEvaluator_invalid(t *testing.T) { - functions := map[string]interface{}{"Hello": hello[interface{}]} + functions := map[string]Factory[any]{"Hello": createFactory("Hello", &struct{}{}, hello)} - p, _ := NewParser[any]( + p, _ := NewParser( functions, testParsePath, componenttest.NewNopTelemetrySettings(), diff --git a/pkg/ottl/contexts/ottldatapoint/datapoint.go b/pkg/ottl/contexts/ottldatapoint/datapoint.go index 1db3a4e73ed2..f0ee9fac8a01 100644 --- a/pkg/ottl/contexts/ottldatapoint/datapoint.go +++ b/pkg/ottl/contexts/ottldatapoint/datapoint.go @@ -77,7 +77,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottllog/log.go b/pkg/ottl/contexts/ottllog/log.go index 42b22e6ffdea..cdad23e34420 100644 --- a/pkg/ottl/contexts/ottllog/log.go +++ b/pkg/ottl/contexts/ottllog/log.go @@ -66,7 +66,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottlmetric/metrics.go b/pkg/ottl/contexts/ottlmetric/metrics.go index 7538eb40dbf4..4d611c53e4cb 100644 --- a/pkg/ottl/contexts/ottlmetric/metrics.go +++ b/pkg/ottl/contexts/ottlmetric/metrics.go @@ -65,7 +65,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottlresource/resource.go b/pkg/ottl/contexts/ottlresource/resource.go index e28bcc875525..346d76e55567 100644 --- a/pkg/ottl/contexts/ottlresource/resource.go +++ b/pkg/ottl/contexts/ottlresource/resource.go @@ -50,7 +50,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottlscope/scope.go b/pkg/ottl/contexts/ottlscope/scope.go index c32fb2833b32..c60dfea720a9 100644 --- a/pkg/ottl/contexts/ottlscope/scope.go +++ b/pkg/ottl/contexts/ottlscope/scope.go @@ -57,7 +57,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottlspan/span.go b/pkg/ottl/contexts/ottlspan/span.go index ef7571248edc..190d62b366f6 100644 --- a/pkg/ottl/contexts/ottlspan/span.go +++ b/pkg/ottl/contexts/ottlspan/span.go @@ -64,7 +64,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/contexts/ottlspanevent/span_events.go b/pkg/ottl/contexts/ottlspanevent/span_events.go index 99b601339cbf..0b8f2eda9d41 100644 --- a/pkg/ottl/contexts/ottlspanevent/span_events.go +++ b/pkg/ottl/contexts/ottlspanevent/span_events.go @@ -72,7 +72,7 @@ func (tCtx TransformContext) getCache() pcommon.Map { return tCtx.cache } -func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { +func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { p, err := ottl.NewParser[TransformContext]( functions, parsePath, diff --git a/pkg/ottl/expression_test.go b/pkg/ottl/expression_test.go index 0cd4ce646cfb..06a164e15e77 100644 --- a/pkg/ottl/expression_test.go +++ b/pkg/ottl/expression_test.go @@ -25,8 +25,8 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" ) -func hello[K any]() (ExprFunc[K], error) { - return func(ctx context.Context, tCtx K) (interface{}, error) { +func hello() (ExprFunc[any], error) { + return func(ctx context.Context, tCtx any) (any, error) { return "world", nil }, nil } @@ -289,7 +289,7 @@ func Test_newGetter(t *testing.T) { }, } - functions := map[string]interface{}{"Hello": hello[interface{}]} + functions := CreateFactoryMap(createFactory("Hello", &struct{}{}, hello)) p, _ := NewParser[any]( functions, diff --git a/pkg/ottl/factory.go b/pkg/ottl/factory.go new file mode 100644 index 000000000000..77678a0f1b97 --- /dev/null +++ b/pkg/ottl/factory.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + +import "go.opentelemetry.io/collector/component" + +// Arguments holds the arguments for an OTTL function, with arguments +// specified as fields on a struct. Argument ordering is defined +type Arguments interface{} + +// FunctionContext contains data provided by the Collector +// component to the OTTL for use in functions. +type FunctionContext struct { + Set component.TelemetrySettings +} + +// Factory defines an OTTL function factory that will generate an OTTL +// function to be called based on an invocation within a statement. +type Factory[K any] interface { + // Name is the canonical name to be used by the user when invocating + // the function generated by this Factory. + Name() string + + // CreateDefaultArguments initializes an Arguments struct specific to this + // Factory containing the arguments for the function. + CreateDefaultArguments() Arguments + + // CreateFunction creates an OTTL function that will use the given Arguments. + CreateFunction(fCtx FunctionContext, args Arguments) (ExprFunc[K], error) + + // Disallow implementations outside this package. + unexportedFactoryFunc() +} + +type CreateFunctionFunc[K any] func(fCtx FunctionContext, args Arguments) (ExprFunc[K], error) + +type factory[K any] struct { + name string + args Arguments + createFunctionFunc CreateFunctionFunc[K] +} + +// nolint:unused +func (f *factory[K]) unexportedFactoryFunc() {} + +func (f *factory[K]) Name() string { + return f.name +} + +func (f *factory[K]) CreateDefaultArguments() Arguments { + return f.args +} + +func (f *factory[K]) CreateFunction(fCtx FunctionContext, args Arguments) (ExprFunc[K], error) { + return f.createFunctionFunc(fCtx, args) +} + +type FactoryOption[K any] func(factory *factory[K]) + +func NewFactory[K any](name string, args Arguments, createFunctionFunc CreateFunctionFunc[K], options ...FactoryOption[K]) Factory[K] { + f := &factory[K]{ + name: name, + args: args, + createFunctionFunc: createFunctionFunc, + } + + for _, option := range options { + option(f) + } + + return f +} + +// CreateFactoryMap takes a list of factories and returns a map of Factories +// keyed on their canonical names. +func CreateFactoryMap[K any](factories ...Factory[K]) map[string]Factory[K] { + factoryMap := map[string]Factory[K]{} + + for _, fn := range factories { + factoryMap[fn.Name()] = fn + } + + return factoryMap +} diff --git a/pkg/ottl/functions.go b/pkg/ottl/functions.go index a5d903c2b7e2..f14efff22960 100644 --- a/pkg/ottl/functions.go +++ b/pkg/ottl/functions.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" "go.opentelemetry.io/collector/pdata/pcommon" @@ -34,65 +35,74 @@ func (p *Parser[K]) newFunctionCall(inv invocation) (Expr[K], error) { if !ok { return Expr[K]{}, fmt.Errorf("undefined function %v", inv.Function) } - args, err := p.buildArgs(inv, reflect.TypeOf(f)) + args := f.CreateDefaultArguments() + + // A nil value indicates the function takes no arguments. + if args != nil { + // Pointer values are necessary to fulfill the Go reflection + // settability requirements. Non-pointer values are not + // modifiable through reflection. + if reflect.TypeOf(args).Kind() != reflect.Pointer { + return Expr[K]{}, fmt.Errorf("factory for %s must return a pointer to an Arguments value in its CreateDefaultArguments method", inv.Function) + } + + err := p.buildArgs(inv, reflect.ValueOf(args).Elem()) + if err != nil { + return Expr[K]{}, fmt.Errorf("error while parsing arguments for call to '%v': %w", inv.Function, err) + } + } + + fn, err := f.CreateFunction(FunctionContext{Set: p.telemetrySettings}, args) if err != nil { - return Expr[K]{}, fmt.Errorf("error while parsing arguments for call to '%v': %w", inv.Function, err) + return Expr[K]{}, fmt.Errorf("couldn't create function: %w", err) } - returnVals := reflect.ValueOf(f).Call(args) + return Expr[K]{exprFunc: fn}, err +} - if returnVals[1].IsNil() { - err = nil - } else { - err = returnVals[1].Interface().(error) +func (p *Parser[K]) buildArgs(inv invocation, argsVal reflect.Value) error { + if len(inv.Arguments) != argsVal.NumField() { + return fmt.Errorf("incorrect number of arguments. Expected: %d Received: %d", argsVal.NumField(), len(inv.Arguments)) } - return Expr[K]{exprFunc: returnVals[0].Interface().(ExprFunc[K])}, err -} + argsType := argsVal.Type() + + for i := 0; i < argsVal.NumField(); i++ { + field := argsVal.Field(i) + fieldType := field.Type() + + fieldTag, ok := argsType.Field(i).Tag.Lookup("ottlarg") -func (p *Parser[K]) buildArgs(inv invocation, fType reflect.Type) ([]reflect.Value, error) { - var args []reflect.Value - // Some function arguments may be intended to take values from the calling processor - // instead of being passed by the caller of the OTTL function, so we have to keep - // track of the index of the argument passed within the DSL. - // e.g. TelemetrySettings, which is provided by the processor to the OTTL Parser struct. - DSLArgumentIndex := 0 - for i := 0; i < fType.NumIn(); i++ { - argType := fType.In(i) - - arg, isInternalArg := p.buildInternalArg(argType) - if isInternalArg { - args = append(args, reflect.ValueOf(arg)) - continue + if !ok { + return fmt.Errorf("no `ottlarg` struct tag on Arguments field '%s'", argsType.Field(i).Name) + } + + argNum, err := strconv.Atoi(fieldTag) + + if err != nil { + return fmt.Errorf("ottlarg struct tag on field '%s' is not a valid integer: %w", argsType.Field(i).Name, err) } - if DSLArgumentIndex >= len(inv.Arguments) { - return nil, fmt.Errorf("not enough arguments") + if argNum < 0 || argNum >= len(inv.Arguments) { + return fmt.Errorf("ottlarg struct tag on field '%s' has value %d, but must be between 0 and %d", argsType.Field(i).Name, argNum, len(inv.Arguments)) } - argVal := inv.Arguments[DSLArgumentIndex] + argVal := inv.Arguments[argNum] var val any - var err error - if argType.Kind() == reflect.Slice { - val, err = p.buildSliceArg(argVal, argType) + if fieldType.Kind() == reflect.Slice { + val, err = p.buildSliceArg(argVal, fieldType) } else { - val, err = p.buildArg(argVal, argType) + val, err = p.buildArg(argVal, fieldType) } if err != nil { - return nil, fmt.Errorf("invalid argument at position %v: %w", DSLArgumentIndex, err) + return fmt.Errorf("invalid argument at position %v: %w", i, err) } - args = append(args, reflect.ValueOf(val)) - - DSLArgumentIndex++ + field.Set(reflect.ValueOf(val)) } - if len(inv.Arguments) > DSLArgumentIndex { - return nil, fmt.Errorf("too many arguments") - } - - return args, nil + return nil } func (p *Parser[K]) buildSliceArg(argVal value, argType reflect.Type) (any, error) { @@ -226,15 +236,6 @@ func (p *Parser[K]) buildArg(argVal value, argType reflect.Type) (any, error) { } } -// Handle interfaces that can be declared as parameters to a OTTL function, but will -// never be called in an invocation. Returns whether the arg is an internal arg. -func (p *Parser[K]) buildInternalArg(argType reflect.Type) (any, bool) { - if argType.Name() == "TelemetrySettings" { - return p.telemetrySettings, true - } - return nil, false -} - type buildArgFunc func(value, reflect.Type) (any, error) func buildSlice[T any](argVal value, argType reflect.Type, buildArg buildArgFunc, name string) (any, error) { diff --git a/pkg/ottl/functions_test.go b/pkg/ottl/functions_test.go index 880502bd2832..fdf4581847a3 100644 --- a/pkg/ottl/functions_test.go +++ b/pkg/ottl/functions_test.go @@ -17,28 +17,91 @@ package ottl import ( "context" "errors" + "fmt" + "reflect" "testing" "github.com/stretchr/testify/assert" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componenttest" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" ) func Test_NewFunctionCall_invalid(t *testing.T) { - functions := make(map[string]interface{}) - functions["testing_error"] = functionThatHasAnError - functions["testing_getsetter"] = functionWithGetSetter - functions["testing_getter"] = functionWithGetter - functions["testing_multiple_args"] = functionWithMultipleArgs - functions["testing_string"] = functionWithString - functions["testing_string_slice"] = functionWithStringSlice - functions["testing_byte_slice"] = functionWithByteSlice - functions["testing_enum"] = functionWithEnum - functions["testing_telemetry_settings_first"] = functionWithTelemetrySettingsFirst - - p, _ := NewParser[any]( + functions := CreateFactoryMap( + createFactory( + "testing_error", + &errorFunctionArguments{}, + functionThatHasAnError, + ), + createFactory[any]( + "testing_getsetter", + &getSetterArguments{}, + functionWithGetSetter, + ), + createFactory[any]( + "testing_getter", + &getterArguments{}, + functionWithGetter, + ), + createFactory[any]( + "testing_multiple_args", + &multipleArgsArguments{}, + functionWithMultipleArgs, + ), + createFactory[any]( + "testing_string", + &stringArguments{}, + functionWithString, + ), + createFactory( + "testing_string_slice", + &stringSliceArguments{}, + functionWithStringSlice, + ), + createFactory( + "testing_byte_slice", + &byteSliceArguments{}, + functionWithByteSlice, + ), + createFactory[any]( + "testing_enum", + &enumArguments{}, + functionWithEnum, + ), + createFactory( + "non_pointer", + errorFunctionArguments{}, + functionThatHasAnError, + ), + createFactory( + "no_struct_tag", + &noStructTagFunctionArguments{}, + functionThatHasAnError, + ), + createFactory( + "wrong_struct_tag", + &wrongTagFunctionArguments{}, + functionThatHasAnError, + ), + createFactory( + "bad_struct_tag", + &badStructTagFunctionArguments{}, + functionThatHasAnError, + ), + createFactory( + "negative_struct_tag", + &negativeStructTagFunctionArguments{}, + functionThatHasAnError, + ), + createFactory( + "out_of_bounds_struct_tag", + &outOfBoundsStructTagFunctionArguments{}, + functionThatHasAnError, + ), + ) + + p, _ := NewParser( functions, testParsePath, componenttest.NewNopTelemetrySettings(), @@ -259,18 +322,80 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, }, }, + { + name: "factory definition uses a non-pointer Arguments value", + inv: invocation{ + Function: "non_pointer", + }, + }, + { + name: "no struct tags", + inv: invocation{ + Function: "no_struct_tag", + Arguments: []value{ + { + String: ottltest.Strp("str"), + }, + }, + }, + }, + { + name: "using the wrong struct tag", + inv: invocation{ + Function: "wrong_struct_tag", + Arguments: []value{ + { + String: ottltest.Strp("str"), + }, + }, + }, + }, + { + name: "non-integer struct tags", + inv: invocation{ + Function: "bad_struct_tag", + Arguments: []value{ + { + String: ottltest.Strp("str"), + }, + }, + }, + }, + { + name: "struct tag index too low", + inv: invocation{ + Function: "negative_struct_tag", + Arguments: []value{ + { + String: ottltest.Strp("str"), + }, + }, + }, + }, + { + name: "struct tag index too high", + inv: invocation{ + Function: "out_of_bounds_struct_tag", + Arguments: []value{ + { + String: ottltest.Strp("str"), + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := p.newFunctionCall(tt.inv) + t.Log(err) assert.Error(t, err) }) } } func Test_NewFunctionCall(t *testing.T) { - p, _ := NewParser[any]( + p, _ := NewParser( defaultFunctionsForTests(), testParsePath, componenttest.NewNopTelemetrySettings(), @@ -282,6 +407,20 @@ func Test_NewFunctionCall(t *testing.T) { inv invocation want any }{ + { + name: "no arguments", + inv: invocation{ + Function: "testing_noop", + Arguments: []value{ + { + List: &list{ + Values: []value{}, + }, + }, + }, + }, + want: nil, + }, { name: "empty slice arg", inv: invocation{ @@ -872,84 +1011,6 @@ func Test_NewFunctionCall(t *testing.T) { }, want: nil, }, - { - name: "telemetrySettings first", - inv: invocation{ - Function: "testing_telemetry_settings_first", - Arguments: []value{ - { - String: ottltest.Strp("test0"), - }, - { - List: &list{ - Values: []value{ - { - String: ottltest.Strp("test"), - }, - }, - }, - }, - { - Literal: &mathExprLiteral{ - Int: ottltest.Intp(1), - }, - }, - }, - }, - want: nil, - }, - { - name: "telemetrySettings middle", - inv: invocation{ - Function: "testing_telemetry_settings_middle", - Arguments: []value{ - { - String: ottltest.Strp("test0"), - }, - { - List: &list{ - Values: []value{ - { - String: ottltest.Strp("test"), - }, - }, - }, - }, - { - Literal: &mathExprLiteral{ - Int: ottltest.Intp(1), - }, - }, - }, - }, - want: nil, - }, - { - name: "telemetrySettings last", - inv: invocation{ - Function: "testing_telemetry_settings_last", - Arguments: []value{ - { - String: ottltest.Strp("test0"), - }, - { - List: &list{ - Values: []value{ - { - String: ottltest.Strp("test"), - }, - }, - }, - }, - { - Literal: &mathExprLiteral{ - Int: ottltest.Intp(1), - }, - }, - }, - }, - want: nil, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -964,126 +1025,217 @@ func Test_NewFunctionCall(t *testing.T) { } } -func functionWithStringSlice(strs []string) (ExprFunc[interface{}], error) { - return func(context.Context, interface{}) (interface{}, error) { +func functionWithNoArguments() (ExprFunc[any], error) { + return func(context.Context, any) (any, error) { + return nil, nil + }, nil +} + +type stringSliceArguments struct { + Strings []string `ottlarg:"0"` +} + +func functionWithStringSlice(strs []string) (ExprFunc[any], error) { + return func(context.Context, any) (any, error) { return len(strs), nil }, nil } +type floatSliceArguments struct { + Floats []float64 `ottlarg:"0"` +} + func functionWithFloatSlice(floats []float64) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(floats), nil }, nil } +type intSliceArguments struct { + Ints []int64 `ottlarg:"0"` +} + func functionWithIntSlice(ints []int64) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(ints), nil }, nil } +type byteSliceArguments struct { + Bytes []byte `ottlarg:"0"` +} + func functionWithByteSlice(bytes []byte) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(bytes), nil }, nil } +type getterSliceArguments struct { + Getters []Getter[any] `ottlarg:"0"` +} + func functionWithGetterSlice(getters []Getter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(getters), nil }, nil } -func functionWithStringGetterSlice(getters []Getter[interface{}]) (ExprFunc[interface{}], error) { +type stringGetterSliceArguments struct { + StringGetters []StringGetter[any] `ottlarg:"0"` +} + +func functionWithStringGetterSlice(getters []StringGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(getters), nil }, nil } +type pMapGetterSliceArguments struct { + PMapGetters []PMapGetter[any] `ottlarg:"0"` +} + func functionWithPMapGetterSlice(getters []PMapGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(getters), nil }, nil } +type stringLikeGetterSliceArguments struct { + StringLikeGetters []StringLikeGetter[any] `ottlarg:"0"` +} + func functionWithStringLikeGetterSlice(getters []StringLikeGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return len(getters), nil }, nil } +type setterArguments struct { + SetterArg Setter[any] `ottlarg:"0"` +} + func functionWithSetter(Setter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type getSetterArguments struct { + GetSetterArg GetSetter[any] `ottlarg:"0"` +} + func functionWithGetSetter(GetSetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type getterArguments struct { + GetterArg Getter[any] `ottlarg:"0"` +} + func functionWithGetter(Getter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type stringGetterArguments struct { + StringGetterArg StringGetter[any] `ottlarg:"0"` +} + func functionWithStringGetter(StringGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type stringLikeGetterArguments struct { + StringLikeGetterArg StringLikeGetter[any] `ottlarg:"0"` +} + func functionWithStringLikeGetter(StringLikeGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type intGetterArguments struct { + IntGetterArg IntGetter[any] `ottlarg:"0"` +} + func functionWithIntGetter(IntGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type pMapGetterArguments struct { + PMapArg PMapGetter[any] `ottlarg:"0"` +} + func functionWithPMapGetter(PMapGetter[interface{}]) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type stringArguments struct { + StringArg string `ottlarg:"0"` +} + func functionWithString(string) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type floatArguments struct { + FloatArg float64 `ottlarg:"0"` +} + func functionWithFloat(float64) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type intArguments struct { + IntArg int64 `ottlarg:"0"` +} + func functionWithInt(int64) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type boolArguments struct { + BoolArg bool `ottlarg:"0"` +} + func functionWithBool(bool) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type multipleArgsArguments struct { + GetSetterArg GetSetter[any] `ottlarg:"0"` + StringArg string `ottlarg:"1"` + FloatArg float64 `ottlarg:"2"` + IntArg int64 `ottlarg:"3"` +} + func functionWithMultipleArgs(GetSetter[interface{}], string, float64, int64) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } +type errorFunctionArguments struct{} + func functionThatHasAnError() (ExprFunc[interface{}], error) { err := errors.New("testing") return func(context.Context, interface{}) (interface{}, error) { @@ -1091,55 +1243,182 @@ func functionThatHasAnError() (ExprFunc[interface{}], error) { }, err } +type enumArguments struct { + EnumArg Enum `ottlarg:"0"` +} + func functionWithEnum(Enum) (ExprFunc[interface{}], error) { return func(context.Context, interface{}) (interface{}, error) { return "anything", nil }, nil } -func functionWithTelemetrySettingsFirst(component.TelemetrySettings, string, []string, int64) (ExprFunc[interface{}], error) { - return func(context.Context, interface{}) (interface{}, error) { - return "anything", nil - }, nil +type noStructTagFunctionArguments struct { + StringArg string } -func functionWithTelemetrySettingsMiddle(string, []string, component.TelemetrySettings, int64) (ExprFunc[interface{}], error) { - return func(context.Context, interface{}) (interface{}, error) { - return "anything", nil - }, nil +type badStructTagFunctionArguments struct { + StringArg string `ottlarg:"a"` } -func functionWithTelemetrySettingsLast(string, []string, int64, component.TelemetrySettings) (ExprFunc[interface{}], error) { - return func(context.Context, interface{}) (interface{}, error) { - return "anything", nil - }, nil +type negativeStructTagFunctionArguments struct { + StringArg string `ottlarg:"-1"` } -func defaultFunctionsForTests() map[string]interface{} { - functions := make(map[string]interface{}) - functions["testing_string_slice"] = functionWithStringSlice - functions["testing_float_slice"] = functionWithFloatSlice - functions["testing_int_slice"] = functionWithIntSlice - functions["testing_byte_slice"] = functionWithByteSlice - functions["testing_getter_slice"] = functionWithGetterSlice - functions["testing_stringgetter_slice"] = functionWithStringGetterSlice - functions["testing_pmapgetter_slice"] = functionWithPMapGetterSlice - functions["testing_stringlikegetter_slice"] = functionWithStringLikeGetterSlice - functions["testing_setter"] = functionWithSetter - functions["testing_getsetter"] = functionWithGetSetter - functions["testing_getter"] = functionWithGetter - functions["testing_stringgetter"] = functionWithStringGetter - functions["testing_stringlikegetter"] = functionWithStringLikeGetter - functions["testing_intgetter"] = functionWithIntGetter - functions["testing_pmapgetter"] = functionWithPMapGetter - functions["testing_string"] = functionWithString - functions["testing_float"] = functionWithFloat - functions["testing_int"] = functionWithInt - functions["testing_bool"] = functionWithBool - functions["testing_multiple_args"] = functionWithMultipleArgs - functions["testing_enum"] = functionWithEnum - functions["testing_telemetry_settings_first"] = functionWithTelemetrySettingsFirst - functions["testing_telemetry_settings_middle"] = functionWithTelemetrySettingsMiddle - functions["testing_telemetry_settings_last"] = functionWithTelemetrySettingsLast - return functions +type outOfBoundsStructTagFunctionArguments struct { + StringArg string `ottlarg:"1"` +} + +type wrongTagFunctionArguments struct { + StringArg string `argument:"1"` +} + +func createFactory[A any](name string, args A, fn any) Factory[any] { + createFunction := func(fCtx FunctionContext, oArgs Arguments) (ExprFunc[any], error) { + fArgs, ok := oArgs.(A) + + if !ok { + return nil, fmt.Errorf("createFactory args must be of type %T", fArgs) + } + + funcVal := reflect.ValueOf(fn) + + if funcVal.Kind() != reflect.Func { + return nil, fmt.Errorf("a non-function value was passed to createFactory") + } + + argsVal := reflect.ValueOf(fArgs).Elem() + fnArgs := make([]reflect.Value, argsVal.NumField()) + + for i := 0; i < argsVal.NumField(); i++ { + fnArgs[i] = argsVal.Field(i) + } + + out := funcVal.Call(fnArgs) + + if !out[1].IsNil() { + return out[0].Interface().(ExprFunc[any]), out[1].Interface().(error) + } + + return out[0].Interface().(ExprFunc[any]), nil + } + + return NewFactory(name, args, createFunction) +} + +func defaultFunctionsForTests() map[string]Factory[any] { + return CreateFactoryMap( + NewFactory( + "testing_noop", + nil, + func(FunctionContext, Arguments) (ExprFunc[any], error) { + return functionWithNoArguments() + }, + ), + createFactory( + "testing_string_slice", + &stringSliceArguments{}, + functionWithStringSlice, + ), + createFactory( + "testing_float_slice", + &floatSliceArguments{}, + functionWithFloatSlice, + ), + createFactory( + "testing_int_slice", + &intSliceArguments{}, + functionWithIntSlice, + ), + createFactory( + "testing_byte_slice", + &byteSliceArguments{}, + functionWithByteSlice, + ), + createFactory[any]( + "testing_getter_slice", + &getterSliceArguments{}, + functionWithGetterSlice, + ), + createFactory[any]( + "testing_stringgetter_slice", + &stringGetterSliceArguments{}, + functionWithStringGetterSlice, + ), + createFactory[any]( + "testing_stringlikegetter_slice", + &stringLikeGetterSliceArguments{}, + functionWithStringLikeGetterSlice, + ), + createFactory[any]( + "testing_pmapgetter_slice", + &pMapGetterSliceArguments{}, + functionWithPMapGetterSlice, + ), + createFactory[any]( + "testing_setter", + &setterArguments{}, + functionWithSetter, + ), + createFactory[any]( + "testing_getsetter", + &getSetterArguments{}, + functionWithGetSetter, + ), + createFactory[any]( + "testing_getter", + &getterArguments{}, + functionWithGetter, + ), + createFactory[any]( + "testing_stringgetter", + &stringGetterArguments{}, + functionWithStringGetter, + ), + createFactory[any]( + "testing_stringlikegetter", + &stringLikeGetterArguments{}, + functionWithStringLikeGetter, + ), + createFactory[any]( + "testing_intgetter", + &intGetterArguments{}, + functionWithIntGetter, + ), + createFactory[any]( + "testing_pmapgetter", + &pMapGetterArguments{}, + functionWithPMapGetter, + ), + createFactory[any]( + "testing_string", + &stringArguments{}, + functionWithString, + ), + createFactory[any]( + "testing_float", + &floatArguments{}, + functionWithFloat, + ), + createFactory[any]( + "testing_int", + &intArguments{}, + functionWithInt, + ), + createFactory[any]( + "testing_bool", + &boolArguments{}, + functionWithBool, + ), + createFactory[any]( + "testing_multiple_args", + &multipleArgsArguments{}, + functionWithMultipleArgs, + ), + createFactory[any]( + "testing_enum", + &enumArguments{}, + functionWithEnum, + ), + ) } diff --git a/pkg/ottl/math_test.go b/pkg/ottl/math_test.go index 34746ba726b4..89c1367649fa 100644 --- a/pkg/ottl/math_test.go +++ b/pkg/ottl/math_test.go @@ -67,6 +67,10 @@ func threePointOne[K any]() (ExprFunc[K], error) { }, nil } +type sumArguments struct { + Ints []int64 `ottlarg:"0"` +} + //nolint:unparam func sum[K any](ints []int64) (ExprFunc[K], error) { return func(context.Context, K) (interface{}, error) { @@ -206,12 +210,12 @@ func Test_evaluateMathExpression(t *testing.T) { }, } - functions := map[string]interface{}{ - "One": one[any], - "Two": two[any], - "ThreePointOne": threePointOne[any], - "Sum": sum[any], - } + functions := CreateFactoryMap( + createFactory("One", &struct{}{}, one[any]), + createFactory("Two", &struct{}{}, two[any]), + createFactory("ThreePointOne", &struct{}{}, threePointOne[any]), + createFactory("Sum", &sumArguments{}, sum[any]), + ) p, _ := NewParser[any]( functions, @@ -249,12 +253,12 @@ func Test_evaluateMathExpression_error(t *testing.T) { }, } - functions := map[string]interface{}{ - "one": one[any], - "two": two[any], - "threePointOne": threePointOne[any], - "sum": sum[any], - } + functions := CreateFactoryMap( + createFactory("one", &struct{}{}, one[any]), + createFactory("two", &struct{}{}, two[any]), + createFactory("threePointOne", &struct{}{}, threePointOne[any]), + createFactory("sum", &sumArguments{}, sum[any]), + ) p, _ := NewParser[any]( functions, diff --git a/pkg/ottl/ottlfuncs/func_concat.go b/pkg/ottl/ottlfuncs/func_concat.go index 3244d594abff..d2cf0dd5f941 100644 --- a/pkg/ottl/ottlfuncs/func_concat.go +++ b/pkg/ottl/ottlfuncs/func_concat.go @@ -22,7 +22,26 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Concat[K any](vals []ottl.StringLikeGetter[K], delimiter string) (ottl.ExprFunc[K], error) { +type ConcatArguments[K any] struct { + Vals []ottl.StringLikeGetter[K] `ottlarg:"0"` + Delimiter string `ottlarg:"1"` +} + +func NewConcatFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("Concat", &ConcatArguments[K]{}, createConcatFunction[K]) +} + +func createConcatFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ConcatArguments[K]) + + if !ok { + return nil, fmt.Errorf("ConcatFactory args must be of type *ConcatArguments[K]") + } + + return concat(args.Vals, args.Delimiter), nil +} + +func concat[K any](vals []ottl.StringLikeGetter[K], delimiter string) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { builder := strings.Builder{} for i, rv := range vals { @@ -40,5 +59,5 @@ func Concat[K any](vals []ottl.StringLikeGetter[K], delimiter string) (ottl.Expr } } return builder.String(), nil - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_concat_test.go b/pkg/ottl/ottlfuncs/func_concat_test.go index f3b51d921387..60667aa76897 100644 --- a/pkg/ottl/ottlfuncs/func_concat_test.go +++ b/pkg/ottl/ottlfuncs/func_concat_test.go @@ -230,8 +230,7 @@ func Test_concat(t *testing.T) { getters[i] = val } - exprFunc, err := Concat(getters, tt.delimiter) - assert.NoError(t, err) + exprFunc := concat(getters, tt.delimiter) result, err := exprFunc(nil, nil) assert.NoError(t, err) assert.Equal(t, tt.expected, result) @@ -245,8 +244,7 @@ func Test_concat_error(t *testing.T) { return make(chan int), nil }, } - exprFunc, err := Concat[interface{}]([]ottl.StringLikeGetter[interface{}]{target}, "test") - assert.NoError(t, err) - _, err = exprFunc(context.Background(), nil) + exprFunc := concat[interface{}]([]ottl.StringLikeGetter[interface{}]{target}, "test") + _, err := exprFunc(context.Background(), nil) assert.Error(t, err) } diff --git a/pkg/ottl/ottlfuncs/func_convert_case.go b/pkg/ottl/ottlfuncs/func_convert_case.go index 25d48b8a72e7..e3b06a8ab8c1 100644 --- a/pkg/ottl/ottlfuncs/func_convert_case.go +++ b/pkg/ottl/ottlfuncs/func_convert_case.go @@ -24,7 +24,26 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func ConvertCase[K any](target ottl.StringGetter[K], toCase string) (ottl.ExprFunc[K], error) { +type ConvertCaseArguments[K any] struct { + Target ottl.StringGetter[K] `ottlarg:"0"` + ToCase string `ottlarg:"1"` +} + +func NewConvertCaseFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("ConvertCase", &ConvertCaseArguments[K]{}, createConvertCaseFunction[K]) +} + +func createConvertCaseFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ConvertCaseArguments[K]) + + if !ok { + return nil, fmt.Errorf("ConvertCaseFactory args must be of type *ConvertCaseArguments[K]") + } + + return convertCase(args.Target, args.ToCase) +} + +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) } diff --git a/pkg/ottl/ottlfuncs/func_convert_case_test.go b/pkg/ottl/ottlfuncs/func_convert_case_test.go index 74d277709d1f..9126a8c627c0 100644 --- a/pkg/ottl/ottlfuncs/func_convert_case_test.go +++ b/pkg/ottl/ottlfuncs/func_convert_case_test.go @@ -188,7 +188,7 @@ func Test_convertCase(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := ConvertCase(tt.target, tt.toCase) + exprFunc, err := convertCase(tt.target, tt.toCase) assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.NoError(t, err) @@ -215,7 +215,7 @@ func Test_convertCaseError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := ConvertCase(tt.target, tt.toCase) + _, err := convertCase(tt.target, tt.toCase) require.Error(t, err) assert.ErrorContains(t, err, "invalid case: unset, allowed cases are: lower, upper, snake, camel") }) @@ -252,7 +252,7 @@ func Test_convertCaseRuntimeError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := ConvertCase[any](tt.target, tt.toCase) + 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_delete_key.go b/pkg/ottl/ottlfuncs/func_delete_key.go index 1ed51675b002..0ba0f86b52ec 100644 --- a/pkg/ottl/ottlfuncs/func_delete_key.go +++ b/pkg/ottl/ottlfuncs/func_delete_key.go @@ -16,11 +16,31 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "fmt" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func DeleteKey[K any](target ottl.PMapGetter[K], key string) (ottl.ExprFunc[K], error) { +type DeleteKeyArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Key string `ottlarg:"1"` +} + +func NewDeleteKeyFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("delete_key", &DeleteKeyArguments[K]{}, createDeleteKeyFunction[K]) +} + +func createDeleteKeyFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*DeleteKeyArguments[K]) + + if !ok { + return nil, fmt.Errorf("DeleteKeysFactory args must be of type *DeleteKeyArguments[K]") + } + + return deleteKey(args.Target, args.Key), nil +} + +func deleteKey[K any](target ottl.PMapGetter[K], key string) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { val, err := target.Get(ctx, tCtx) if err != nil { @@ -28,5 +48,5 @@ func DeleteKey[K any](target ottl.PMapGetter[K], key string) (ottl.ExprFunc[K], } val.Remove(key) return nil, nil - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_delete_key_test.go b/pkg/ottl/ottlfuncs/func_delete_key_test.go index ad20e9a1994f..3d65fe7a2329 100644 --- a/pkg/ottl/ottlfuncs/func_delete_key_test.go +++ b/pkg/ottl/ottlfuncs/func_delete_key_test.go @@ -76,10 +76,9 @@ func Test_deleteKey(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := DeleteKey(tt.target, tt.key) - assert.NoError(t, err) + exprFunc := deleteKey(tt.target, tt.key) - _, err = exprFunc(nil, scenarioMap) + _, err := exprFunc(nil, scenarioMap) assert.Nil(t, err) expected := pcommon.NewMap() @@ -100,9 +99,8 @@ func Test_deleteKey_bad_input(t *testing.T) { key := "anything" - exprFunc, err := DeleteKey[interface{}](target, key) - assert.NoError(t, err) - _, err = exprFunc(nil, input) + exprFunc := deleteKey[interface{}](target, key) + _, err := exprFunc(nil, input) assert.Error(t, err) } @@ -115,8 +113,7 @@ func Test_deleteKey_get_nil(t *testing.T) { key := "anything" - exprFunc, err := DeleteKey[interface{}](target, key) - assert.NoError(t, err) - _, err = exprFunc(nil, nil) + exprFunc := deleteKey[interface{}](target, key) + _, err := exprFunc(nil, nil) assert.Error(t, err) } diff --git a/pkg/ottl/ottlfuncs/func_delete_matching_keys.go b/pkg/ottl/ottlfuncs/func_delete_matching_keys.go index a6f16f178817..a3ae9315584f 100644 --- a/pkg/ottl/ottlfuncs/func_delete_matching_keys.go +++ b/pkg/ottl/ottlfuncs/func_delete_matching_keys.go @@ -24,7 +24,26 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func DeleteMatchingKeys[K any](target ottl.PMapGetter[K], pattern string) (ottl.ExprFunc[K], error) { +type DeleteMatchingKeysArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` +} + +func NewDeleteMatchingKeysFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("delete_matching_keys", &DeleteMatchingKeysArguments[K]{}, createDeleteMatchingKeysFunction[K]) +} + +func createDeleteMatchingKeysFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*DeleteMatchingKeysArguments[K]) + + if !ok { + return nil, fmt.Errorf("DeleteMatchingKeysFactory args must be of type *DeleteMatchingKeysArguments[K]") + } + + return deleteMatchingKeys(args.Target, args.Pattern) +} + +func deleteMatchingKeys[K any](target ottl.PMapGetter[K], pattern string) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(pattern) if err != nil { return nil, fmt.Errorf("the regex pattern supplied to delete_matching_keys is not a valid pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_delete_matching_keys_test.go b/pkg/ottl/ottlfuncs/func_delete_matching_keys_test.go index 45041e5e5084..4e7a58c5994a 100644 --- a/pkg/ottl/ottlfuncs/func_delete_matching_keys_test.go +++ b/pkg/ottl/ottlfuncs/func_delete_matching_keys_test.go @@ -75,7 +75,7 @@ func Test_deleteMatchingKeys(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := DeleteMatchingKeys(tt.target, tt.pattern) + exprFunc, err := deleteMatchingKeys(tt.target, tt.pattern) assert.NoError(t, err) _, err = exprFunc(nil, scenarioMap) @@ -97,7 +97,7 @@ func Test_deleteMatchingKeys_bad_input(t *testing.T) { }, } - exprFunc, err := DeleteMatchingKeys[interface{}](target, "anything") + exprFunc, err := deleteMatchingKeys[interface{}](target, "anything") assert.NoError(t, err) _, err = exprFunc(nil, input) @@ -111,7 +111,7 @@ func Test_deleteMatchingKeys_get_nil(t *testing.T) { }, } - exprFunc, err := DeleteMatchingKeys[interface{}](target, "anything") + exprFunc, err := deleteMatchingKeys[interface{}](target, "anything") assert.NoError(t, err) _, err = exprFunc(nil, nil) assert.Error(t, err) @@ -126,7 +126,7 @@ func Test_deleteMatchingKeys_invalid_pattern(t *testing.T) { } invalidRegexPattern := "*" - _, err := DeleteMatchingKeys[interface{}](target, invalidRegexPattern) + _, err := deleteMatchingKeys[interface{}](target, invalidRegexPattern) require.Error(t, err) assert.ErrorContains(t, err, "error parsing regexp:") } diff --git a/pkg/ottl/ottlfuncs/func_int.go b/pkg/ottl/ottlfuncs/func_int.go index b53beeac52f7..ad216f3e3e32 100644 --- a/pkg/ottl/ottlfuncs/func_int.go +++ b/pkg/ottl/ottlfuncs/func_int.go @@ -16,12 +16,31 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "fmt" "strconv" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Int[K any](target ottl.Getter[K]) (ottl.ExprFunc[K], error) { +type IntArguments[K any] struct { + Target ottl.Getter[K] `ottlarg:"0"` +} + +func NewIntFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("Int", &IntArguments[K]{}, createIntFunction[K]) +} + +func createIntFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*IntArguments[K]) + + if !ok { + return nil, fmt.Errorf("IntFactory args must be of type *IntArguments[K]") + } + + return intFunc(args.Target), nil +} + +func intFunc[K any](target ottl.Getter[K]) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { value, err := target.Get(ctx, tCtx) if err != nil { @@ -47,5 +66,5 @@ func Int[K any](target ottl.Getter[K]) (ottl.ExprFunc[K], error) { default: return nil, nil } - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_int_test.go b/pkg/ottl/ottlfuncs/func_int_test.go index 1d200cc653c0..231bc1debb5b 100644 --- a/pkg/ottl/ottlfuncs/func_int_test.go +++ b/pkg/ottl/ottlfuncs/func_int_test.go @@ -82,12 +82,11 @@ func Test_Int(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := Int[interface{}](&ottl.StandardGetSetter[interface{}]{ + exprFunc := intFunc[interface{}](&ottl.StandardGetSetter[interface{}]{ Getter: func(context.Context, interface{}) (interface{}, error) { return tt.value, nil }, }) - assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.NoError(t, err) assert.Equal(t, tt.expected, result) diff --git a/pkg/ottl/ottlfuncs/func_is_match.go b/pkg/ottl/ottlfuncs/func_is_match.go index 03e2e2fac460..6ab35ae82215 100644 --- a/pkg/ottl/ottlfuncs/func_is_match.go +++ b/pkg/ottl/ottlfuncs/func_is_match.go @@ -22,7 +22,26 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func IsMatch[K any](target ottl.StringLikeGetter[K], pattern string) (ottl.ExprFunc[K], error) { +type IsMatchArguments[K any] struct { + Target ottl.StringLikeGetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` +} + +func NewIsMatchFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("IsMatch", &IsMatchArguments[K]{}, createIsMatchFunction[K]) +} + +func createIsMatchFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*IsMatchArguments[K]) + + if !ok { + return nil, fmt.Errorf("IsMatchFactory args must be of type *IsMatchArguments[K]") + } + + return isMatch(args.Target, args.Pattern) +} + +func isMatch[K any](target ottl.StringLikeGetter[K], pattern string) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(pattern) if err != nil { return nil, fmt.Errorf("the pattern supplied to IsMatch is not a valid regexp pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_is_match_test.go b/pkg/ottl/ottlfuncs/func_is_match_test.go index afa34dbb763f..05ee0a0aecec 100644 --- a/pkg/ottl/ottlfuncs/func_is_match_test.go +++ b/pkg/ottl/ottlfuncs/func_is_match_test.go @@ -117,7 +117,7 @@ func Test_isMatch(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := IsMatch(tt.target, tt.pattern) + exprFunc, err := isMatch(tt.target, tt.pattern) assert.NoError(t, err) result, err := exprFunc(context.Background(), nil) assert.NoError(t, err) @@ -132,7 +132,7 @@ func Test_isMatch_validation(t *testing.T) { return "anything", nil }, } - _, err := IsMatch[interface{}](target, "\\K") + _, err := isMatch[interface{}](target, "\\K") require.Error(t, err) } @@ -142,7 +142,7 @@ func Test_isMatch_error(t *testing.T) { return make(chan int), nil }, } - exprFunc, err := IsMatch[interface{}](target, "test") + exprFunc, err := isMatch[interface{}](target, "test") assert.NoError(t, err) _, err = exprFunc(context.Background(), nil) require.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_keep_keys.go b/pkg/ottl/ottlfuncs/func_keep_keys.go index be46c87df516..0e680a154037 100644 --- a/pkg/ottl/ottlfuncs/func_keep_keys.go +++ b/pkg/ottl/ottlfuncs/func_keep_keys.go @@ -16,13 +16,33 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "fmt" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func KeepKeys[K any](target ottl.PMapGetter[K], keys []string) (ottl.ExprFunc[K], error) { +type KeepKeysArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Keys []string `ottlarg:"1"` +} + +func NewKeepKeysFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("keep_keys", &KeepKeysArguments[K]{}, createKeepKeysFunction[K]) +} + +func createKeepKeysFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*KeepKeysArguments[K]) + + if !ok { + return nil, fmt.Errorf("KeepKeysFactory args must be of type *KeepKeysArguments[K]") + } + + return keepKeys(args.Target, args.Keys), nil +} + +func keepKeys[K any](target ottl.PMapGetter[K], keys []string) ottl.ExprFunc[K] { keySet := make(map[string]struct{}, len(keys)) for _, key := range keys { keySet[key] = struct{}{} @@ -41,5 +61,5 @@ func KeepKeys[K any](target ottl.PMapGetter[K], keys []string) (ottl.ExprFunc[K] val.Clear() } return nil, nil - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_keep_keys_test.go b/pkg/ottl/ottlfuncs/func_keep_keys_test.go index 87dea8d8041d..8cabedbd49c3 100644 --- a/pkg/ottl/ottlfuncs/func_keep_keys_test.go +++ b/pkg/ottl/ottlfuncs/func_keep_keys_test.go @@ -77,10 +77,9 @@ func Test_keepKeys(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := KeepKeys(tt.target, tt.keys) - assert.NoError(t, err) + exprFunc := keepKeys(tt.target, tt.keys) - _, err = exprFunc(nil, scenarioMap) + _, err := exprFunc(nil, scenarioMap) assert.Nil(t, err) expected := pcommon.NewMap() @@ -101,10 +100,9 @@ func Test_keepKeys_bad_input(t *testing.T) { keys := []string{"anything"} - exprFunc, err := KeepKeys[interface{}](target, keys) - assert.NoError(t, err) + exprFunc := keepKeys[interface{}](target, keys) - _, err = exprFunc(nil, input) + _, err := exprFunc(nil, input) assert.Error(t, err) } @@ -117,8 +115,7 @@ func Test_keepKeys_get_nil(t *testing.T) { keys := []string{"anything"} - exprFunc, err := KeepKeys[interface{}](target, keys) - assert.NoError(t, err) - _, err = exprFunc(nil, nil) + exprFunc := keepKeys[interface{}](target, keys) + _, err := exprFunc(nil, nil) assert.Error(t, err) } diff --git a/pkg/ottl/ottlfuncs/func_limit.go b/pkg/ottl/ottlfuncs/func_limit.go index e34ed23d4d4e..541ffce9c4e3 100644 --- a/pkg/ottl/ottlfuncs/func_limit.go +++ b/pkg/ottl/ottlfuncs/func_limit.go @@ -23,7 +23,27 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Limit[K any](target ottl.PMapGetter[K], limit int64, priorityKeys []string) (ottl.ExprFunc[K], error) { +type LimitArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Limit int64 `ottlarg:"1"` + PriorityKeys []string `ottlarg:"2"` +} + +func NewLimitFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("Limit", &LimitArguments[K]{}, createLimitFunction[K]) +} + +func createLimitFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*LimitArguments[K]) + + if !ok { + return nil, fmt.Errorf("LimitFactory args must be of type *LimitArguments[K]") + } + + return limit(args.Target, args.Limit, args.PriorityKeys) +} + +func limit[K any](target ottl.PMapGetter[K], limit int64, priorityKeys []string) (ottl.ExprFunc[K], error) { if limit < 0 { return nil, fmt.Errorf("invalid limit for limit function, %d cannot be negative", limit) } diff --git a/pkg/ottl/ottlfuncs/func_limit_test.go b/pkg/ottl/ottlfuncs/func_limit_test.go index 0cb14726a415..2bc1ea925d7d 100644 --- a/pkg/ottl/ottlfuncs/func_limit_test.go +++ b/pkg/ottl/ottlfuncs/func_limit_test.go @@ -124,7 +124,7 @@ func Test_limit(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := Limit(tt.target, tt.limit, tt.keep) + exprFunc, err := limit(tt.target, tt.limit, tt.keep) assert.NoError(t, err) result, err := exprFunc(nil, scenarioMap) @@ -160,7 +160,7 @@ func Test_limit_validation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := Limit(tt.target, tt.limit, tt.keep) + _, err := limit(tt.target, tt.limit, tt.keep) assert.Error(t, err) }) } @@ -174,7 +174,7 @@ func Test_limit_bad_input(t *testing.T) { }, } - exprFunc, err := Limit[interface{}](target, 1, []string{}) + exprFunc, err := limit[interface{}](target, 1, []string{}) assert.NoError(t, err) _, err = exprFunc(nil, input) assert.Error(t, err) @@ -187,7 +187,7 @@ func Test_limit_get_nil(t *testing.T) { }, } - exprFunc, err := Limit[interface{}](target, 1, []string{}) + exprFunc, err := limit[interface{}](target, 1, []string{}) assert.NoError(t, err) _, err = exprFunc(nil, nil) assert.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_merge_maps.go b/pkg/ottl/ottlfuncs/func_merge_maps.go index 9e99b7a8b566..d8bd4249b023 100644 --- a/pkg/ottl/ottlfuncs/func_merge_maps.go +++ b/pkg/ottl/ottlfuncs/func_merge_maps.go @@ -29,13 +29,33 @@ const ( UPSERT = "upsert" ) -// MergeMaps function merges the source map into the target map using the supplied strategy to handle conflicts. +type MergeMapsArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Source ottl.PMapGetter[K] `ottlarg:"1"` + Strategy string `ottlarg:"2"` +} + +func NewMergeMapsFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("merge_maps", &MergeMapsArguments[K]{}, createMergeMapsFunction[K]) +} + +func createMergeMapsFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*MergeMapsArguments[K]) + + if !ok { + return nil, fmt.Errorf("MergeMapsFactory args must be of type *MergeMapsArguments[K]") + } + + return mergeMaps(args.Target, args.Source, args.Strategy) +} + +// mergeMaps function merges the source map into the target map using the supplied strategy to handle conflicts. // Strategy definitions: // // insert: Insert the value from `source` into `target` where the key does not already exist. // update: Update the entry in `target` with the value from `source` where the key does exist // upsert: Performs insert or update. Insert the value from `source` into `target` where the key does not already exist and update the entry in `target` with the value from `source` where the key does exist. -func MergeMaps[K any](target ottl.PMapGetter[K], source ottl.PMapGetter[K], strategy string) (ottl.ExprFunc[K], error) { +func mergeMaps[K any](target ottl.PMapGetter[K], source ottl.PMapGetter[K], strategy string) (ottl.ExprFunc[K], error) { if strategy != INSERT && strategy != UPDATE && strategy != UPSERT { return nil, fmt.Errorf("invalid value for strategy, %v, must be 'insert', 'update' or 'upsert'", strategy) } diff --git a/pkg/ottl/ottlfuncs/func_merge_maps_test.go b/pkg/ottl/ottlfuncs/func_merge_maps_test.go index d405dc551637..d1c400acf14e 100644 --- a/pkg/ottl/ottlfuncs/func_merge_maps_test.go +++ b/pkg/ottl/ottlfuncs/func_merge_maps_test.go @@ -137,7 +137,7 @@ func Test_MergeMaps(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := MergeMaps[pcommon.Map](targetGetter, tt.source, tt.strategy) + exprFunc, err := mergeMaps[pcommon.Map](targetGetter, tt.source, tt.strategy) assert.NoError(t, err) result, err := exprFunc(context.Background(), scenarioMap) @@ -164,7 +164,7 @@ func Test_MergeMaps_bad_target(t *testing.T) { }, } - exprFunc, err := MergeMaps[interface{}](target, input, "insert") + exprFunc, err := mergeMaps[interface{}](target, input, "insert") assert.NoError(t, err) _, err = exprFunc(nil, input) assert.Error(t, err) @@ -182,7 +182,7 @@ func Test_MergeMaps_bad_input(t *testing.T) { }, } - exprFunc, err := MergeMaps[interface{}](target, input, "insert") + exprFunc, err := mergeMaps[interface{}](target, input, "insert") assert.NoError(t, err) _, err = exprFunc(nil, input) assert.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_parse_json.go b/pkg/ottl/ottlfuncs/func_parse_json.go index ae9c5db1c1ef..8f299ede2aea 100644 --- a/pkg/ottl/ottlfuncs/func_parse_json.go +++ b/pkg/ottl/ottlfuncs/func_parse_json.go @@ -16,6 +16,7 @@ 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" @@ -23,7 +24,25 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -// ParseJSON returns a `pcommon.Map` struct that is a result of parsing the target string as JSON +type ParseJSONArguments[K any] struct { + Target ottl.StringGetter[K] `ottlarg:"0"` +} + +func NewParseJSONFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("ParseJSON", &ParseJSONArguments[K]{}, createParseJSONFunction[K]) +} + +func createParseJSONFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ParseJSONArguments[K]) + + if !ok { + return nil, fmt.Errorf("ParseJSONFactory args must be of type *ParseJSONArguments[K]") + } + + return parseJSON(args.Target), nil +} + +// parseJSON returns a `pcommon.Map` struct that is a result of parsing the target string as JSON // Each JSON type is converted into a `pdata.Value` using the following map: // // JSON boolean -> bool @@ -32,7 +51,7 @@ import ( // JSON null -> nil // JSON arrays -> pdata.SliceValue // JSON objects -> map[string]any -func ParseJSON[K any](target ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { +func parseJSON[K any](target ottl.StringGetter[K]) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { targetVal, err := target.Get(ctx, tCtx) if err != nil { @@ -46,5 +65,5 @@ func ParseJSON[K any](target ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { result := pcommon.NewMap() err = result.FromRaw(parsedValue) return result, err - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_parse_json_test.go b/pkg/ottl/ottlfuncs/func_parse_json_test.go index e64cc32df2b7..bde237ac52a7 100644 --- a/pkg/ottl/ottlfuncs/func_parse_json_test.go +++ b/pkg/ottl/ottlfuncs/func_parse_json_test.go @@ -148,9 +148,7 @@ func Test_ParseJSON(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := ParseJSON(tt.target) - assert.NoError(t, err) - + exprFunc := parseJSON(tt.target) result, err := exprFunc(context.Background(), nil) assert.NoError(t, err) @@ -177,8 +175,7 @@ func Test_ParseJSON_Error(t *testing.T) { return 1, nil }, } - exprFunc, err := ParseJSON[interface{}](target) - assert.NoError(t, err) - _, err = exprFunc(context.Background(), nil) + exprFunc := parseJSON[interface{}](target) + _, err := exprFunc(context.Background(), nil) assert.Error(t, err) } diff --git a/pkg/ottl/ottlfuncs/func_replace_all_matches.go b/pkg/ottl/ottlfuncs/func_replace_all_matches.go index 78535ab65cf9..6993e0360f19 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_matches.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_matches.go @@ -24,7 +24,27 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func ReplaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { +type ReplaceAllMatchesArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` + Replacement string `ottlarg:"2"` +} + +func NewReplaceAllMatchesFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("replace_all_matches", &ReplaceAllMatchesArguments[K]{}, createReplaceAllMatchesFunction[K]) +} + +func createReplaceAllMatchesFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ReplaceAllMatchesArguments[K]) + + if !ok { + return nil, fmt.Errorf("ReplaceAllMatchesFactory args must be of type *ReplaceAllMatchesArguments[K]") + } + + return replaceAllMatches(args.Target, args.Pattern, args.Replacement) +} + +func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { glob, err := glob.Compile(pattern) if err != nil { return nil, fmt.Errorf("the pattern supplied to replace_match is not a valid pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go b/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go index 3e4d1ef45eb0..7dc1f31e1abd 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go @@ -71,7 +71,7 @@ func Test_replaceAllMatches(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := ReplaceAllMatches(tt.target, tt.pattern, tt.replacement) + exprFunc, err := replaceAllMatches(tt.target, tt.pattern, tt.replacement) assert.NoError(t, err) result, err := exprFunc(nil, scenarioMap) @@ -94,7 +94,7 @@ func Test_replaceAllMatches_bad_input(t *testing.T) { }, } - exprFunc, err := ReplaceAllMatches[interface{}](target, "*", "{replacement}") + exprFunc, err := replaceAllMatches[interface{}](target, "*", "{replacement}") assert.NoError(t, err) _, err = exprFunc(nil, input) assert.Error(t, err) @@ -107,7 +107,7 @@ func Test_replaceAllMatches_get_nil(t *testing.T) { }, } - exprFunc, err := ReplaceAllMatches[interface{}](target, "*", "{anything}") + exprFunc, err := replaceAllMatches[interface{}](target, "*", "{anything}") assert.NoError(t, err) _, err = exprFunc(nil, nil) assert.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_patterns.go b/pkg/ottl/ottlfuncs/func_replace_all_patterns.go index 9f22769227a3..b722ddbdadc4 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_patterns.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_patterns.go @@ -29,7 +29,28 @@ const ( modeValue = "value" ) -func ReplaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPattern string, replacement string) (ottl.ExprFunc[K], error) { +type ReplaceAllPatternsArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Mode string `ottlarg:"1"` + RegexPattern string `ottlarg:"2"` + Replacement string `ottlarg:"3"` +} + +func NewReplaceAllPatternsFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("replace_all_patterns", &ReplaceAllPatternsArguments[K]{}, createReplaceAllPatternsFunction[K]) +} + +func createReplaceAllPatternsFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ReplaceAllPatternsArguments[K]) + + if !ok { + return nil, fmt.Errorf("ReplaceAllPatternsFactory args must be of type *ReplaceAllPatternsArguments[K]") + } + + return replaceAllPatterns(args.Target, args.Mode, args.RegexPattern, args.Replacement) +} + +func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPattern string, replacement string) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(regexPattern) if err != nil { return nil, fmt.Errorf("the regex pattern supplied to replace_all_patterns is not a valid pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go b/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go index 996df1a01d7a..4c3876a6da06 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go @@ -179,7 +179,7 @@ func Test_replaceAllPatterns(t *testing.T) { scenarioMap := pcommon.NewMap() input.CopyTo(scenarioMap) - exprFunc, err := ReplaceAllPatterns[pcommon.Map](tt.target, tt.mode, tt.pattern, tt.replacement) + exprFunc, err := replaceAllPatterns[pcommon.Map](tt.target, tt.mode, tt.pattern, tt.replacement) assert.NoError(t, err) _, err = exprFunc(nil, scenarioMap) @@ -202,7 +202,7 @@ func Test_replaceAllPatterns_bad_input(t *testing.T) { }, } - exprFunc, err := ReplaceAllPatterns[interface{}](target, modeValue, "regexpattern", "{replacement}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexpattern", "{replacement}") assert.Nil(t, err) _, err = exprFunc(nil, input) @@ -216,7 +216,7 @@ func Test_replaceAllPatterns_get_nil(t *testing.T) { }, } - exprFunc, err := ReplaceAllPatterns[interface{}](target, modeValue, "regexp", "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexp", "{anything}") assert.NoError(t, err) _, err = exprFunc(nil, nil) @@ -232,7 +232,7 @@ func Test_replaceAllPatterns_invalid_pattern(t *testing.T) { } invalidRegexPattern := "*" - exprFunc, err := ReplaceAllPatterns[interface{}](target, modeValue, invalidRegexPattern, "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, invalidRegexPattern, "{anything}") require.Error(t, err) assert.ErrorContains(t, err, "error parsing regexp:") assert.Nil(t, exprFunc) @@ -247,7 +247,7 @@ func Test_replaceAllPatterns_invalid_model(t *testing.T) { } invalidMode := "invalid" - exprFunc, err := ReplaceAllPatterns[interface{}](target, invalidMode, "regex", "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, invalidMode, "regex", "{anything}") assert.Nil(t, exprFunc) assert.Contains(t, err.Error(), "invalid mode") } diff --git a/pkg/ottl/ottlfuncs/func_replace_match.go b/pkg/ottl/ottlfuncs/func_replace_match.go index db5796a32456..83945cf190d5 100644 --- a/pkg/ottl/ottlfuncs/func_replace_match.go +++ b/pkg/ottl/ottlfuncs/func_replace_match.go @@ -23,7 +23,27 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func ReplaceMatch[K any](target ottl.GetSetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { +type ReplaceMatchArguments[K any] struct { + Target ottl.GetSetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` + Replacement string `ottlarg:"2"` +} + +func NewReplaceMatchFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("replace_match", &ReplaceMatchArguments[K]{}, createReplaceMatchFunction[K]) +} + +func createReplaceMatchFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ReplaceMatchArguments[K]) + + if !ok { + return nil, fmt.Errorf("ReplaceMatchFactory args must be of type *ReplaceMatchArguments[K]") + } + + return replaceMatch(args.Target, args.Pattern, args.Replacement) +} + +func replaceMatch[K any](target ottl.GetSetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { glob, err := glob.Compile(pattern) if err != nil { return nil, fmt.Errorf("the pattern supplied to replace_match is not a valid pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_replace_match_test.go b/pkg/ottl/ottlfuncs/func_replace_match_test.go index a414c41f0f51..4f385b087ad0 100644 --- a/pkg/ottl/ottlfuncs/func_replace_match_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_match_test.go @@ -67,7 +67,7 @@ func Test_replaceMatch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { scenarioValue := pcommon.NewValueStr(input.Str()) - exprFunc, err := ReplaceMatch(tt.target, tt.pattern, tt.replacement) + exprFunc, err := replaceMatch(tt.target, tt.pattern, tt.replacement) assert.NoError(t, err) result, err := exprFunc(nil, scenarioValue) assert.NoError(t, err) @@ -93,7 +93,7 @@ func Test_replaceMatch_bad_input(t *testing.T) { }, } - exprFunc, err := ReplaceMatch[interface{}](target, "*", "{replacement}") + exprFunc, err := replaceMatch[interface{}](target, "*", "{replacement}") assert.NoError(t, err) result, err := exprFunc(nil, input) @@ -114,7 +114,7 @@ func Test_replaceMatch_get_nil(t *testing.T) { }, } - exprFunc, err := ReplaceMatch[interface{}](target, "*", "{anything}") + exprFunc, err := replaceMatch[interface{}](target, "*", "{anything}") assert.NoError(t, err) result, err := exprFunc(nil, nil) diff --git a/pkg/ottl/ottlfuncs/func_replace_pattern.go b/pkg/ottl/ottlfuncs/func_replace_pattern.go index 57fdb3e0b4ee..01a7a4493225 100644 --- a/pkg/ottl/ottlfuncs/func_replace_pattern.go +++ b/pkg/ottl/ottlfuncs/func_replace_pattern.go @@ -22,7 +22,27 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func ReplacePattern[K any](target ottl.GetSetter[K], regexPattern string, replacement string) (ottl.ExprFunc[K], error) { +type ReplacePatternArguments[K any] struct { + Target ottl.GetSetter[K] `ottlarg:"0"` + RegexPattern string `ottlarg:"1"` + Replacement string `ottlarg:"2"` +} + +func NewReplacePatternFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("replace_pattern", &ReplacePatternArguments[K]{}, createReplacePatternFunction[K]) +} + +func createReplacePatternFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*ReplacePatternArguments[K]) + + if !ok { + return nil, fmt.Errorf("ReplacePatternFactory args must be of type *ReplacePatternArguments[K]") + } + + return replacePattern(args.Target, args.RegexPattern, args.Replacement) +} + +func replacePattern[K any](target ottl.GetSetter[K], regexPattern string, replacement string) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(regexPattern) if err != nil { return nil, fmt.Errorf("the regex pattern supplied to replace_pattern is not a valid pattern: %w", err) diff --git a/pkg/ottl/ottlfuncs/func_replace_pattern_test.go b/pkg/ottl/ottlfuncs/func_replace_pattern_test.go index fe51fac55825..b44e77f9e905 100644 --- a/pkg/ottl/ottlfuncs/func_replace_pattern_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_pattern_test.go @@ -95,7 +95,7 @@ func Test_replacePattern(t *testing.T) { t.Run(tt.name, func(t *testing.T) { scenarioValue := pcommon.NewValueStr(input.Str()) - exprFunc, err := ReplacePattern(tt.target, tt.pattern, tt.replacement) + exprFunc, err := replacePattern(tt.target, tt.pattern, tt.replacement) assert.NoError(t, err) result, err := exprFunc(nil, scenarioValue) @@ -122,7 +122,7 @@ func Test_replacePattern_bad_input(t *testing.T) { }, } - exprFunc, err := ReplacePattern[interface{}](target, "regexp", "{replacement}") + exprFunc, err := replacePattern[interface{}](target, "regexp", "{replacement}") assert.NoError(t, err) result, err := exprFunc(nil, input) @@ -142,7 +142,7 @@ func Test_replacePattern_get_nil(t *testing.T) { }, } - exprFunc, err := ReplacePattern[interface{}](target, `nomatch\=[^\s]*(\s?)`, "{anything}") + exprFunc, err := replacePattern[interface{}](target, `nomatch\=[^\s]*(\s?)`, "{anything}") assert.NoError(t, err) result, err := exprFunc(nil, nil) @@ -163,7 +163,7 @@ func Test_replacePatterns_invalid_pattern(t *testing.T) { } invalidRegexPattern := "*" - _, err := ReplacePattern[interface{}](target, invalidRegexPattern, "{anything}") + _, err := replacePattern[interface{}](target, invalidRegexPattern, "{anything}") require.Error(t, err) assert.ErrorContains(t, err, "error parsing regexp:") } diff --git a/pkg/ottl/ottlfuncs/func_set.go b/pkg/ottl/ottlfuncs/func_set.go index 617cd39383c8..a043d6d1d6b3 100644 --- a/pkg/ottl/ottlfuncs/func_set.go +++ b/pkg/ottl/ottlfuncs/func_set.go @@ -16,11 +16,31 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "fmt" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Set[K any](target ottl.Setter[K], value ottl.Getter[K]) (ottl.ExprFunc[K], error) { +type SetArguments[K any] struct { + Target ottl.Setter[K] `ottlarg:"0"` + Value ottl.Getter[K] `ottlarg:"1"` +} + +func NewSetFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("set", &SetArguments[K]{}, createSetFunction[K]) +} + +func createSetFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*SetArguments[K]) + + if !ok { + return nil, fmt.Errorf("SetFactory args must be of type *SetArguments[K]") + } + + return set(args.Target, args.Value), nil +} + +func set[K any](target ottl.Setter[K], value ottl.Getter[K]) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { val, err := value.Get(ctx, tCtx) if err != nil { @@ -35,5 +55,5 @@ func Set[K any](target ottl.Setter[K], value ottl.Getter[K]) (ottl.ExprFunc[K], } } return nil, nil - }, nil + } } diff --git a/pkg/ottl/ottlfuncs/func_set_test.go b/pkg/ottl/ottlfuncs/func_set_test.go index 8751957a4e4b..1228e668869d 100644 --- a/pkg/ottl/ottlfuncs/func_set_test.go +++ b/pkg/ottl/ottlfuncs/func_set_test.go @@ -69,8 +69,7 @@ func Test_set(t *testing.T) { t.Run(tt.name, func(t *testing.T) { scenarioValue := pcommon.NewValueStr(input.Str()) - exprFunc, err := Set(tt.setter, tt.getter) - assert.NoError(t, err) + exprFunc := set(tt.setter, tt.getter) result, err := exprFunc(nil, scenarioValue) assert.NoError(t, err) @@ -98,8 +97,7 @@ func Test_set_get_nil(t *testing.T) { }, } - exprFunc, err := Set[interface{}](setter, getter) - assert.NoError(t, err) + exprFunc := set[interface{}](setter, getter) result, err := exprFunc(nil, nil) assert.NoError(t, err) diff --git a/pkg/ottl/ottlfuncs/func_span_id.go b/pkg/ottl/ottlfuncs/func_span_id.go index 1ec1febe88bd..76e15a4eb98a 100644 --- a/pkg/ottl/ottlfuncs/func_span_id.go +++ b/pkg/ottl/ottlfuncs/func_span_id.go @@ -17,13 +17,32 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" "errors" + "fmt" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func SpanID[K any](bytes []byte) (ottl.ExprFunc[K], error) { +type SpanIDArguments[K any] struct { + Bytes []byte `ottlarg:"0"` +} + +func NewSpanIDFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("SpanID", &SpanIDArguments[K]{}, createSpanIDFunction[K]) +} + +func createSpanIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*SpanIDArguments[K]) + + if !ok { + return nil, fmt.Errorf("SpanIDFactory args must be of type *SpanIDArguments[K]") + } + + return spanID[K](args.Bytes) +} + +func spanID[K any](bytes []byte) (ottl.ExprFunc[K], error) { if len(bytes) != 8 { return nil, errors.New("span ids must be 8 bytes") } diff --git a/pkg/ottl/ottlfuncs/func_span_id_test.go b/pkg/ottl/ottlfuncs/func_span_id_test.go index c1532990418e..a41bfb82c549 100644 --- a/pkg/ottl/ottlfuncs/func_span_id_test.go +++ b/pkg/ottl/ottlfuncs/func_span_id_test.go @@ -36,7 +36,7 @@ func Test_spanID(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := SpanID[interface{}](tt.bytes) + exprFunc, err := spanID[interface{}](tt.bytes) assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.NoError(t, err) @@ -61,7 +61,7 @@ func Test_spanID_validation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := SpanID[interface{}](tt.bytes) + _, err := spanID[interface{}](tt.bytes) require.Error(t, err) assert.ErrorContains(t, err, "span ids must be 8 bytes") }) diff --git a/pkg/ottl/ottlfuncs/func_split.go b/pkg/ottl/ottlfuncs/func_split.go index 5f370c7354d4..55c12b115107 100644 --- a/pkg/ottl/ottlfuncs/func_split.go +++ b/pkg/ottl/ottlfuncs/func_split.go @@ -16,17 +16,37 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "fmt" "strings" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Split[K any](target ottl.StringGetter[K], delimiter string) (ottl.ExprFunc[K], error) { +type SplitArguments[K any] struct { + Target ottl.StringGetter[K] `ottlarg:"0"` + Delimiter string `ottlarg:"1"` +} + +func NewSplitFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("Split", &SplitArguments[K]{}, createSplitFunction[K]) +} + +func createSplitFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*SplitArguments[K]) + + if !ok { + return nil, fmt.Errorf("SplitFactory args must be of type *SplitArguments[K]") + } + + return split(args.Target, args.Delimiter), nil +} + +func split[K any](target ottl.StringGetter[K], delimiter string) ottl.ExprFunc[K] { return func(ctx context.Context, tCtx K) (interface{}, error) { val, err := target.Get(ctx, tCtx) if err != nil { return nil, err } 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 7ad8d3e32615..cdd5602d4edd 100644 --- a/pkg/ottl/ottlfuncs/func_split_test.go +++ b/pkg/ottl/ottlfuncs/func_split_test.go @@ -73,8 +73,7 @@ func Test_split(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := Split(tt.target, tt.delimiter) - assert.NoError(t, err) + exprFunc := split(tt.target, tt.delimiter) result, err := exprFunc(nil, nil) assert.NoError(t, err) assert.Equal(t, tt.expected, result) @@ -88,8 +87,7 @@ func Test_Split_Error(t *testing.T) { return 1, nil }, } - exprFunc, err := Split[interface{}](target, ",") - assert.NoError(t, err) - _, err = exprFunc(context.Background(), nil) + exprFunc := split[interface{}](target, ",") + _, 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 10cdee594124..379aaf889507 100644 --- a/pkg/ottl/ottlfuncs/func_substring.go +++ b/pkg/ottl/ottlfuncs/func_substring.go @@ -21,7 +21,27 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func Substring[K any](target ottl.StringGetter[K], start int64, length int64) (ottl.ExprFunc[K], error) { +type SubstringArguments[K any] struct { + Target ottl.StringGetter[K] `ottlarg:"0"` + Start int64 `ottlarg:"1"` + Length int64 `ottlarg:"2"` +} + +func NewSubstringFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("Substring", &SubstringArguments[K]{}, createSubstringFunction[K]) +} + +func createSubstringFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*SubstringArguments[K]) + + if !ok { + return nil, fmt.Errorf("SubstringFactory args must be of type *SubstringArguments[K]") + } + + return substring(args.Target, args.Start, args.Length) +} + +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) } diff --git a/pkg/ottl/ottlfuncs/func_substring_test.go b/pkg/ottl/ottlfuncs/func_substring_test.go index 389479709d71..e98a5df1fe0a 100644 --- a/pkg/ottl/ottlfuncs/func_substring_test.go +++ b/pkg/ottl/ottlfuncs/func_substring_test.go @@ -56,7 +56,7 @@ func Test_substring(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := Substring(tt.target, tt.start, tt.length) + exprFunc, err := substring(tt.target, tt.start, tt.length) assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.NoError(t, err) @@ -95,7 +95,7 @@ func Test_substring_validation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := Substring(tt.target, tt.start, tt.length) + _, err := substring(tt.target, tt.start, tt.length) assert.Error(t, err) }) } @@ -151,7 +151,7 @@ func Test_substring_error(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := Substring(tt.target, tt.start, tt.length) + exprFunc, err := substring(tt.target, tt.start, tt.length) assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_trace_id.go b/pkg/ottl/ottlfuncs/func_trace_id.go index e09438a620aa..0833204e02d0 100644 --- a/pkg/ottl/ottlfuncs/func_trace_id.go +++ b/pkg/ottl/ottlfuncs/func_trace_id.go @@ -17,13 +17,32 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" "errors" + "fmt" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) -func TraceID[K any](bytes []byte) (ottl.ExprFunc[K], error) { +type TraceIDArguments[K any] struct { + Bytes []byte `ottlarg:"0"` +} + +func NewTraceIDFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("TraceID", &TraceIDArguments[K]{}, createTraceIDFunction[K]) +} + +func createTraceIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*TraceIDArguments[K]) + + if !ok { + return nil, fmt.Errorf("TraceIDFactory args must be of type *TraceIDArguments[K]") + } + + return traceID[K](args.Bytes) +} + +func traceID[K any](bytes []byte) (ottl.ExprFunc[K], error) { if len(bytes) != 16 { return nil, errors.New("traces ids must be 16 bytes") } diff --git a/pkg/ottl/ottlfuncs/func_trace_id_test.go b/pkg/ottl/ottlfuncs/func_trace_id_test.go index 0ad485dab190..69a50151dd19 100644 --- a/pkg/ottl/ottlfuncs/func_trace_id_test.go +++ b/pkg/ottl/ottlfuncs/func_trace_id_test.go @@ -36,7 +36,7 @@ func Test_traceID(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exprFunc, err := TraceID[interface{}](tt.bytes) + exprFunc, err := traceID[interface{}](tt.bytes) assert.NoError(t, err) result, err := exprFunc(nil, nil) assert.NoError(t, err) @@ -61,7 +61,7 @@ func Test_traceID_validation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := TraceID[interface{}](tt.bytes) + _, err := traceID[interface{}](tt.bytes) require.Error(t, err) assert.ErrorContains(t, err, "traces ids must be 16 bytes") }) diff --git a/pkg/ottl/ottlfuncs/func_truncate_all.go b/pkg/ottl/ottlfuncs/func_truncate_all.go index 894c256b44f4..7a95609d5748 100644 --- a/pkg/ottl/ottlfuncs/func_truncate_all.go +++ b/pkg/ottl/ottlfuncs/func_truncate_all.go @@ -23,6 +23,25 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) +type TruncateAllArguments[K any] struct { + Target ottl.PMapGetter[K] `ottlarg:"0"` + Limit int64 `ottlarg:"1"` +} + +func NewTruncateAllFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("truncate_all", &TruncateAllArguments[K]{}, createTruncateAllFunction[K]) +} + +func createTruncateAllFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*TruncateAllArguments[K]) + + if !ok { + return nil, fmt.Errorf("TruncateAllFactory args must be of type *TruncateAllArguments[K]") + } + + return TruncateAll(args.Target, args.Limit) +} + func TruncateAll[K any](target ottl.PMapGetter[K], limit int64) (ottl.ExprFunc[K], error) { if limit < 0 { return nil, fmt.Errorf("invalid limit for truncate_all function, %d cannot be negative", limit) diff --git a/pkg/ottl/parser.go b/pkg/ottl/parser.go index afc98dc9d4b7..54bf827bb74f 100644 --- a/pkg/ottl/parser.go +++ b/pkg/ottl/parser.go @@ -43,7 +43,7 @@ func (e *ErrorMode) UnmarshalText(text []byte) error { } type Parser[K any] struct { - functions map[string]interface{} + functions map[string]Factory[K] pathParser PathExpressionParser[K] enumParser EnumParser telemetrySettings component.TelemetrySettings @@ -77,7 +77,7 @@ func (s *Statement[K]) Execute(ctx context.Context, tCtx K) (any, bool, error) { } func NewParser[K any]( - functions map[string]interface{}, + functions map[string]Factory[K], pathParser PathExpressionParser[K], settings component.TelemetrySettings, options ...Option[K], diff --git a/processor/filterprocessor/internal/common/functions.go b/processor/filterprocessor/internal/common/functions.go index ce8d56b96a6d..23c8af9b3516 100644 --- a/processor/filterprocessor/internal/common/functions.go +++ b/processor/filterprocessor/internal/common/functions.go @@ -25,19 +25,59 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric" ) -func MetricFunctions() map[string]interface{} { +func MetricFunctions() map[string]ottl.Factory[ottlmetric.TransformContext] { funcs := filterottl.StandardMetricFuncs() - funcs["HasAttrKeyOnDatapoint"] = hasAttributeKeyOnDatapoint - funcs["HasAttrOnDatapoint"] = hasAttributeOnDatapoint + hasAttributeKeyOnDatapoint := newHasAttributeKeyOnDatapointFactory() + funcs[hasAttributeKeyOnDatapoint.Name()] = hasAttributeKeyOnDatapoint + + hasAttributeOnDatapoint := newHasAttributeOnDatapointFactory() + funcs[hasAttributeOnDatapoint.Name()] = hasAttributeOnDatapoint return funcs } +type hasAttributeOnDatapointArguments struct { + Key string `ottlarg:"0"` + ExpectedVal string `ottlarg:"1"` +} + +func newHasAttributeOnDatapointFactory() ottl.Factory[ottlmetric.TransformContext] { + return ottl.NewFactory("HasAttrOnDatapoint", &hasAttributeOnDatapointArguments{}, createHasAttributeOnDatapointFunction) +} + +func createHasAttributeOnDatapointFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlmetric.TransformContext], error) { + args, ok := oArgs.(*hasAttributeOnDatapointArguments) + + if !ok { + return nil, fmt.Errorf("hasAttributeOnDatapointFactory args must be of type *hasAttributeOnDatapointArguments") + } + + return hasAttributeOnDatapoint(args.Key, args.ExpectedVal) +} + func hasAttributeOnDatapoint(key string, expectedVal string) (ottl.ExprFunc[ottlmetric.TransformContext], error) { return func(ctx context.Context, tCtx ottlmetric.TransformContext) (interface{}, error) { return checkDataPoints(tCtx, key, &expectedVal) }, nil } +type hasAttributeKeyOnDatapointArguments struct { + Key string `ottlarg:"0"` +} + +func newHasAttributeKeyOnDatapointFactory() ottl.Factory[ottlmetric.TransformContext] { + return ottl.NewFactory("HasAttrKeyOnDatapoint", &hasAttributeKeyOnDatapointArguments{}, createHasAttributeKeyOnDatapointFunction) +} + +func createHasAttributeKeyOnDatapointFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlmetric.TransformContext], error) { + args, ok := oArgs.(*hasAttributeOnDatapointArguments) + + if !ok { + return nil, fmt.Errorf("hasAttributeKeyOnDatapointFactory args must be of type *hasAttributeOnDatapointArguments") + } + + return hasAttributeKeyOnDatapoint(args.Key) +} + func hasAttributeKeyOnDatapoint(key string) (ottl.ExprFunc[ottlmetric.TransformContext], error) { return func(ctx context.Context, tCtx ottlmetric.TransformContext) (interface{}, error) { return checkDataPoints(tCtx, key, nil) diff --git a/processor/routingprocessor/internal/common/functions.go b/processor/routingprocessor/internal/common/functions.go index b948caf5ad03..31c058662812 100644 --- a/processor/routingprocessor/internal/common/functions.go +++ b/processor/routingprocessor/internal/common/functions.go @@ -21,17 +21,19 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" ) -func Functions[K any]() map[string]interface{} { - return map[string]interface{}{ - "IsMatch": ottlfuncs.IsMatch[K], - "delete_key": ottlfuncs.DeleteKey[K], - "delete_matching_keys": ottlfuncs.DeleteMatchingKeys[K], +func createRouteFunction[K any](_ ottl.FunctionContext, _ ottl.Arguments) (ottl.ExprFunc[K], error) { + return func(context.Context, K) (interface{}, error) { + return true, nil + }, nil +} + +func Functions[K any]() map[string]ottl.Factory[K] { + return ottl.CreateFactoryMap( + ottlfuncs.NewIsMatchFactory[K](), + ottlfuncs.NewDeleteKeyFactory[K](), + ottlfuncs.NewDeleteMatchingKeysFactory[K](), // noop function, it is required since the parsing of conditions is not implemented yet, - // see https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/13545 - "route": func() (ottl.ExprFunc[K], error) { - return func(context.Context, K) (interface{}, error) { - return true, nil - }, nil - }, - } + ////github.com/open-telemetry/opentelemetry-collector-contrib/issues/13545 + ottl.NewFactory("route", nil, createRouteFunction[K]), + ) } diff --git a/processor/transformprocessor/internal/common/functions.go b/processor/transformprocessor/internal/common/functions.go index 9bc22cbd0174..159803f5c319 100644 --- a/processor/transformprocessor/internal/common/functions.go +++ b/processor/transformprocessor/internal/common/functions.go @@ -15,40 +15,41 @@ package common // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlresource" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlscope" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" ) -func Functions[K any]() map[string]interface{} { - return map[string]interface{}{ - "TraceID": ottlfuncs.TraceID[K], - "SpanID": ottlfuncs.SpanID[K], - "IsMatch": ottlfuncs.IsMatch[K], - "Concat": ottlfuncs.Concat[K], - "Split": ottlfuncs.Split[K], - "Int": ottlfuncs.Int[K], - "ConvertCase": ottlfuncs.ConvertCase[K], - "ParseJSON": ottlfuncs.ParseJSON[K], - "Substring": ottlfuncs.Substring[K], - "keep_keys": ottlfuncs.KeepKeys[K], - "set": ottlfuncs.Set[K], - "truncate_all": ottlfuncs.TruncateAll[K], - "limit": ottlfuncs.Limit[K], - "replace_match": ottlfuncs.ReplaceMatch[K], - "replace_all_matches": ottlfuncs.ReplaceAllMatches[K], - "replace_pattern": ottlfuncs.ReplacePattern[K], - "replace_all_patterns": ottlfuncs.ReplaceAllPatterns[K], - "delete_key": ottlfuncs.DeleteKey[K], - "delete_matching_keys": ottlfuncs.DeleteMatchingKeys[K], - "merge_maps": ottlfuncs.MergeMaps[K], - } +func Functions[K any]() map[string]ottl.Factory[K] { + return ottl.CreateFactoryMap( + ottlfuncs.NewTraceIDFactory[K](), + ottlfuncs.NewSpanIDFactory[K](), + ottlfuncs.NewIsMatchFactory[K](), + ottlfuncs.NewConcatFactory[K](), + ottlfuncs.NewSplitFactory[K](), + ottlfuncs.NewIntFactory[K](), + ottlfuncs.NewConvertCaseFactory[K](), + ottlfuncs.NewParseJSONFactory[K](), + ottlfuncs.NewSubstringFactory[K](), + ottlfuncs.NewKeepKeysFactory[K](), + ottlfuncs.NewSetFactory[K](), + ottlfuncs.NewTruncateAllFactory[K](), + ottlfuncs.NewLimitFactory[K](), + ottlfuncs.NewReplaceMatchFactory[K](), + ottlfuncs.NewReplaceAllMatchesFactory[K](), + ottlfuncs.NewReplacePatternFactory[K](), + ottlfuncs.NewReplaceAllPatternsFactory[K](), + ottlfuncs.NewDeleteKeyFactory[K](), + ottlfuncs.NewDeleteMatchingKeysFactory[K](), + ottlfuncs.NewMergeMapsFactory[K](), + ) } -func ResourceFunctions() map[string]interface{} { +func ResourceFunctions() map[string]ottl.Factory[ottlresource.TransformContext] { return Functions[ottlresource.TransformContext]() } -func ScopeFunctions() map[string]interface{} { +func ScopeFunctions() map[string]ottl.Factory[ottlscope.TransformContext] { return Functions[ottlscope.TransformContext]() } diff --git a/processor/transformprocessor/internal/common/logs.go b/processor/transformprocessor/internal/common/logs.go index 2b9ab45dfd9e..d0bd7a550a10 100644 --- a/processor/transformprocessor/internal/common/logs.go +++ b/processor/transformprocessor/internal/common/logs.go @@ -64,7 +64,7 @@ type LogParserCollection struct { type LogParserCollectionOption func(*LogParserCollection) error -func WithLogParser(functions map[string]interface{}) LogParserCollectionOption { +func WithLogParser(functions map[string]ottl.Factory[ottllog.TransformContext]) LogParserCollectionOption { return func(lp *LogParserCollection) error { logParser, err := ottllog.NewParser(functions, lp.settings) if err != nil { diff --git a/processor/transformprocessor/internal/common/metrics.go b/processor/transformprocessor/internal/common/metrics.go index 4d27e9c6f2b9..0785dfbc50b0 100644 --- a/processor/transformprocessor/internal/common/metrics.go +++ b/processor/transformprocessor/internal/common/metrics.go @@ -153,7 +153,7 @@ type MetricParserCollection struct { type MetricParserCollectionOption func(*MetricParserCollection) error -func WithMetricParser(functions map[string]interface{}) MetricParserCollectionOption { +func WithMetricParser(functions map[string]ottl.Factory[ottlmetric.TransformContext]) MetricParserCollectionOption { return func(mp *MetricParserCollection) error { metricParser, err := ottlmetric.NewParser(functions, mp.settings) if err != nil { @@ -164,7 +164,7 @@ func WithMetricParser(functions map[string]interface{}) MetricParserCollectionOp } } -func WithDataPointParser(functions map[string]interface{}) MetricParserCollectionOption { +func WithDataPointParser(functions map[string]ottl.Factory[ottldatapoint.TransformContext]) MetricParserCollectionOption { return func(mp *MetricParserCollection) error { dataPointParser, err := ottldatapoint.NewParser(functions, mp.settings) if err != nil { diff --git a/processor/transformprocessor/internal/common/traces.go b/processor/transformprocessor/internal/common/traces.go index 73a7f03b685c..59d097525c58 100644 --- a/processor/transformprocessor/internal/common/traces.go +++ b/processor/transformprocessor/internal/common/traces.go @@ -100,7 +100,7 @@ type TraceParserCollection struct { type TraceParserCollectionOption func(*TraceParserCollection) error -func WithSpanParser(functions map[string]interface{}) TraceParserCollectionOption { +func WithSpanParser(functions map[string]ottl.Factory[ottlspan.TransformContext]) TraceParserCollectionOption { return func(tp *TraceParserCollection) error { spanParser, err := ottlspan.NewParser(functions, tp.settings) if err != nil { @@ -111,7 +111,7 @@ func WithSpanParser(functions map[string]interface{}) TraceParserCollectionOptio } } -func WithSpanEventParser(functions map[string]interface{}) TraceParserCollectionOption { +func WithSpanEventParser(functions map[string]ottl.Factory[ottlspanevent.TransformContext]) TraceParserCollectionOption { return func(tp *TraceParserCollection) error { spanEventParser, err := ottlspanevent.NewParser(functions, tp.settings) if err != nil { diff --git a/processor/transformprocessor/internal/logs/functions.go b/processor/transformprocessor/internal/logs/functions.go index 6378c293b3d9..def558789bdd 100644 --- a/processor/transformprocessor/internal/logs/functions.go +++ b/processor/transformprocessor/internal/logs/functions.go @@ -15,11 +15,12 @@ package logs // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/logs" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common" ) -func LogFunctions() map[string]interface{} { +func LogFunctions() map[string]ottl.Factory[ottllog.TransformContext] { // No logs-only functions yet. return common.Functions[ottllog.TransformContext]() } diff --git a/processor/transformprocessor/internal/metrics/func_convert_gauge_to_sum.go b/processor/transformprocessor/internal/metrics/func_convert_gauge_to_sum.go index cca5bb4928f8..7c511ae2eb5e 100644 --- a/processor/transformprocessor/internal/metrics/func_convert_gauge_to_sum.go +++ b/processor/transformprocessor/internal/metrics/func_convert_gauge_to_sum.go @@ -24,6 +24,25 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint" ) +type convertGaugeToSumArguments struct { + StringAggTemp string `ottlarg:"0"` + Monotonic bool `ottlarg:"1"` +} + +func newConvertGaugeToSumFactory() ottl.Factory[ottldatapoint.TransformContext] { + return ottl.NewFactory("convert_gauge_to_sum", &convertGaugeToSumArguments{}, createConvertGaugeToSumFunction) +} + +func createConvertGaugeToSumFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { + args, ok := oArgs.(*convertGaugeToSumArguments) + + if !ok { + return nil, fmt.Errorf("ConvertGaugeToSumFactory args must be of type *ConvertGaugeToSumArguments") + } + + return convertGaugeToSum(args.StringAggTemp, args.Monotonic) +} + func convertGaugeToSum(stringAggTemp string, monotonic bool) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { var aggTemp pmetric.AggregationTemporality switch stringAggTemp { diff --git a/processor/transformprocessor/internal/metrics/func_convert_sum_to_gauge.go b/processor/transformprocessor/internal/metrics/func_convert_sum_to_gauge.go index aad2b38ef38d..9972df608294 100644 --- a/processor/transformprocessor/internal/metrics/func_convert_sum_to_gauge.go +++ b/processor/transformprocessor/internal/metrics/func_convert_sum_to_gauge.go @@ -23,6 +23,14 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint" ) +func newConvertSumToGaugeFactory() ottl.Factory[ottldatapoint.TransformContext] { + return ottl.NewFactory("convert_sum_to_gauge", nil, createConvertSumToGaugeFunction) +} + +func createConvertSumToGaugeFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { + return convertSumToGauge() +} + func convertSumToGauge() (ottl.ExprFunc[ottldatapoint.TransformContext], error) { return func(_ context.Context, tCtx ottldatapoint.TransformContext) (interface{}, error) { metric := tCtx.GetMetric() diff --git a/processor/transformprocessor/internal/metrics/func_convert_summary_count_val_to_sum.go b/processor/transformprocessor/internal/metrics/func_convert_summary_count_val_to_sum.go index 8b6240018e61..4319a9bb7e96 100644 --- a/processor/transformprocessor/internal/metrics/func_convert_summary_count_val_to_sum.go +++ b/processor/transformprocessor/internal/metrics/func_convert_summary_count_val_to_sum.go @@ -24,6 +24,25 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint" ) +type convertSummaryCountValToSumArguments struct { + StringAggTemp string `ottlarg:"0"` + Monotonic bool `ottlarg:"1"` +} + +func newConvertSummaryCountValToSumFactory() ottl.Factory[ottldatapoint.TransformContext] { + return ottl.NewFactory("convert_summary_count_val_to_sum", &convertSummaryCountValToSumArguments{}, createConvertSummaryCountValToSumFunction) +} + +func createConvertSummaryCountValToSumFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { + args, ok := oArgs.(*convertSummaryCountValToSumArguments) + + if !ok { + return nil, fmt.Errorf("convertSummaryCountValToSumFactory args must be of type *convertSummaryCountValToSumArguments") + } + + return convertSummaryCountValToSum(args.StringAggTemp, args.Monotonic) +} + func convertSummaryCountValToSum(stringAggTemp string, monotonic bool) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { var aggTemp pmetric.AggregationTemporality switch stringAggTemp { diff --git a/processor/transformprocessor/internal/metrics/func_convert_summary_sum_val_to_sum.go b/processor/transformprocessor/internal/metrics/func_convert_summary_sum_val_to_sum.go index 71b7bb824cbe..59b2fa4b120c 100644 --- a/processor/transformprocessor/internal/metrics/func_convert_summary_sum_val_to_sum.go +++ b/processor/transformprocessor/internal/metrics/func_convert_summary_sum_val_to_sum.go @@ -24,6 +24,25 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint" ) +type convertSummarySumValToSumArguments struct { + StringAggTemp string `ottlarg:"0"` + Monotonic bool `ottlarg:"1"` +} + +func newConvertSummarySumValToSumFactory() ottl.Factory[ottldatapoint.TransformContext] { + return ottl.NewFactory("convert_summary_sum_val_to_sum", &convertSummarySumValToSumArguments{}, createConvertSummarySumValToSumFunction) +} + +func createConvertSummarySumValToSumFunction(fCtx ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { + args, ok := oArgs.(*convertSummarySumValToSumArguments) + + if !ok { + return nil, fmt.Errorf("convertSummarySumValToSumFactory args must be of type *convertSummarySumValToSumArguments") + } + + return convertSummarySumValToSum(args.StringAggTemp, args.Monotonic) +} + func convertSummarySumValToSum(stringAggTemp string, monotonic bool) (ottl.ExprFunc[ottldatapoint.TransformContext], error) { var aggTemp pmetric.AggregationTemporality switch stringAggTemp { diff --git a/processor/transformprocessor/internal/metrics/functions.go b/processor/transformprocessor/internal/metrics/functions.go index e9459380ef31..73afb4660151 100644 --- a/processor/transformprocessor/internal/metrics/functions.go +++ b/processor/transformprocessor/internal/metrics/functions.go @@ -15,30 +15,29 @@ package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/metrics" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common" ) -// registry is a map of names to functions for metrics pipelines -var datapointRegistry = map[string]interface{}{ - "convert_sum_to_gauge": convertSumToGauge, - "convert_gauge_to_sum": convertGaugeToSum, - "convert_summary_sum_val_to_sum": convertSummarySumValToSum, - "convert_summary_count_val_to_sum": convertSummaryCountValToSum, -} +func DataPointFunctions() map[string]ottl.Factory[ottldatapoint.TransformContext] { + functions := common.Functions[ottldatapoint.TransformContext]() + + datapointFunctions := ottl.CreateFactoryMap[ottldatapoint.TransformContext]( + newConvertSumToGaugeFactory(), + newConvertGaugeToSumFactory(), + newConvertSummarySumValToSumFactory(), + newConvertSummaryCountValToSumFactory(), + ) -func init() { - // Init metrics registry with default functions common to all signals - for k, v := range common.Functions[ottldatapoint.TransformContext]() { - datapointRegistry[k] = v + for k, v := range datapointFunctions { + functions[k] = v } -} -func DataPointFunctions() map[string]interface{} { - return datapointRegistry + return functions } -func MetricFunctions() map[string]interface{} { +func MetricFunctions() map[string]ottl.Factory[ottlmetric.TransformContext] { return common.Functions[ottlmetric.TransformContext]() } diff --git a/processor/transformprocessor/internal/metrics/functions_test.go b/processor/transformprocessor/internal/metrics/functions_test.go index 5ded6a8b37a5..f641c03d5b8a 100644 --- a/processor/transformprocessor/internal/metrics/functions_test.go +++ b/processor/transformprocessor/internal/metrics/functions_test.go @@ -27,10 +27,10 @@ import ( func Test_DataPointFunctions(t *testing.T) { expected := common.Functions[ottldatapoint.TransformContext]() - expected["convert_sum_to_gauge"] = convertSumToGauge - expected["convert_gauge_to_sum"] = convertGaugeToSum - expected["convert_summary_sum_val_to_sum"] = convertSummarySumValToSum - expected["convert_summary_count_val_to_sum"] = convertSummaryCountValToSum + expected["convert_sum_to_gauge"] = newConvertSumToGaugeFactory() + expected["convert_gauge_to_sum"] = newConvertGaugeToSumFactory() + expected["convert_summary_sum_val_to_sum"] = newConvertSummarySumValToSumFactory() + expected["convert_summary_count_val_to_sum"] = newConvertSummaryCountValToSumFactory() actual := DataPointFunctions() diff --git a/processor/transformprocessor/internal/traces/functions.go b/processor/transformprocessor/internal/traces/functions.go index 31c2b9f57b3c..3157380a5608 100644 --- a/processor/transformprocessor/internal/traces/functions.go +++ b/processor/transformprocessor/internal/traces/functions.go @@ -15,17 +15,18 @@ package traces // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/traces" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspanevent" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common" ) -func SpanFunctions() map[string]interface{} { +func SpanFunctions() map[string]ottl.Factory[ottlspan.TransformContext] { // No trace-only functions yet. return common.Functions[ottlspan.TransformContext]() } -func SpanEventFunctions() map[string]interface{} { +func SpanEventFunctions() map[string]ottl.Factory[ottlspanevent.TransformContext] { // No trace-only functions yet. return common.Functions[ottlspanevent.TransformContext]() }