diff --git a/pkg/ottl/parser.go b/pkg/ottl/parser.go index 2f053098c8c3..2dcefb199126 100644 --- a/pkg/ottl/parser.go +++ b/pkg/ottl/parser.go @@ -21,6 +21,14 @@ import ( "github.com/alecthomas/participle/v2" "go.opentelemetry.io/collector/component" "go.uber.org/multierr" + "go.uber.org/zap" +) + +type ErrorMode int + +const ( + IgnoreError ErrorMode = iota + PropagateError ) type Parser[K any] struct { @@ -147,3 +155,25 @@ func newParser[G any]() *participle.Parser[G] { } return parser } + +// Statements represents a list of statements that will be executed sequentially for a TransformContext. +type Statements[K any] struct { + statements []Statement[K] + errorMode ErrorMode + telemetrySettings component.TelemetrySettings +} + +// Execute is a function that will execute all the statements in the Statements list. +func (s *Statements[K]) Execute(ctx context.Context, tCtx K) error { + for _, statement := range s.statements { + _, _, err := statement.Execute(ctx, tCtx) + if err != nil { + if s.errorMode == PropagateError { + err = fmt.Errorf("failed to execute statement: %w", err) + return err + } + s.telemetrySettings.Logger.Error("failed to execute statement", zap.Error(err)) + } + } + return nil +} diff --git a/pkg/ottl/parser_test.go b/pkg/ottl/parser_test.go index a7a86169c35d..406c1f759e47 100644 --- a/pkg/ottl/parser_test.go +++ b/pkg/ottl/parser_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componenttest" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" ) @@ -1310,3 +1311,74 @@ func Test_Execute(t *testing.T) { }) } } + +func Test_Execute_Error(t *testing.T) { + tests := []struct { + name string + condition boolExpressionEvaluator[interface{}] + function ExprFunc[interface{}] + errorMode ErrorMode + }{ + { + name: "IgnoreError error from condition", + condition: func(context.Context, interface{}) (bool, error) { + return true, fmt.Errorf("test") + }, + function: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1, nil + }, + errorMode: IgnoreError, + }, + { + name: "PropagateError error from condition", + condition: func(context.Context, interface{}) (bool, error) { + return true, fmt.Errorf("test") + }, + function: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1, nil + }, + errorMode: PropagateError, + }, + { + name: "IgnoreError error from function", + condition: func(context.Context, interface{}) (bool, error) { + return true, nil + }, + function: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1, fmt.Errorf("test") + }, + errorMode: IgnoreError, + }, + { + name: "PropagateError error from function", + condition: func(context.Context, interface{}) (bool, error) { + return true, nil + }, + function: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1, fmt.Errorf("test") + }, + errorMode: PropagateError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + statements := Statements[interface{}]{ + statements: []Statement[interface{}]{ + { + condition: BoolExpr[any]{tt.condition}, + function: Expr[any]{exprFunc: tt.function}, + }, + }, + errorMode: tt.errorMode, + telemetrySettings: componenttest.NewNopTelemetrySettings(), + } + + err := statements.Execute(context.Background(), nil) + if tt.errorMode == PropagateError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +}