diff --git a/.chloggen/ottl-add-statement-context-append-utility.yaml b/.chloggen/ottl-add-statement-context-append-utility.yaml deleted file mode 100644 index 3852f6a3edef..000000000000 --- a/.chloggen/ottl-add-statement-context-append-utility.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Use this changelog template to create an entry for release notes. - -# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' -change_type: enhancement - -# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) -component: pkg/ottl - -# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: Add OTTL parser utility to rewrite statements appending missing paths context - -# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [29017] - -# (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.Parser[K]` has a new function `AppendStatementPathsContext`" - -# If your change doesn't affect end users or the exported elements of any package, -# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. -# Optional: The change log or logs in which this entry should be included. -# e.g. '[user]' or '[user, api]' -# Include 'user' if the change is relevant to end users. -# Include 'api' if there is a change to a library API. -# Default: '[user]' -change_logs: [api] diff --git a/pkg/ottl/parser.go b/pkg/ottl/parser.go index aec7b1ac9335..f16f0e3b0fbb 100644 --- a/pkg/ottl/parser.go +++ b/pkg/ottl/parser.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "sort" "strings" "github.com/alecthomas/participle/v2" @@ -196,11 +197,11 @@ func (p *Parser[K]) ParseCondition(condition string) (*Condition[K], error) { }, nil } -// appendStatementPathsContext changes the given OTTL statement adding the context name prefix +// prependContextToStatementPaths changes the given OTTL statement adding the context name prefix // to all context-less paths. No modifications are performed for paths which [Path.Context] // value matches any WithPathContextNames value. // The context argument must be valid WithPathContextNames value, otherwise an error is returned. -func (p *Parser[K]) appendStatementPathsContext(context string, statement string) (string, error) { +func (p *Parser[K]) prependContextToStatementPaths(context string, statement string) (string, error) { if _, ok := p.pathContextNames[context]; !ok { return statement, fmt.Errorf(`unknown context "%s" for parser %T, valid options are: %s`, context, p, p.buildPathContextNamesText("")) } @@ -220,7 +221,7 @@ func (p *Parser[K]) appendStatementPathsContext(context string, statement string } } - return writeStatementWithPathsContext(context, statement, missingContextOffsets), nil + return insertContextIntoStatementOffsets(context, statement, missingContextOffsets) } var parser = newParser[parsedStatement]() @@ -254,27 +255,28 @@ func parseCondition(raw string) (*booleanExpression, error) { return parsed, nil } -func writeStatementWithPathsContext(context string, statement string, offsets []int) string { +func insertContextIntoStatementOffsets(context string, statement string, offsets []int) (string, error) { if len(offsets) == 0 { - return statement + return statement, nil } contextPrefix := context + "." var sb strings.Builder sb.Grow(len(statement) + (len(contextPrefix) * len(offsets))) + sort.Ints(offsets) left := 0 - for i, offset := range offsets { + for _, offset := range offsets { + if offset < 0 || offset > len(statement) { + return statement, fmt.Errorf(`failed to insert context "%s" into statement "%s": offset %d is out of range`, context, statement, offset) + } sb.WriteString(statement[left:offset]) sb.WriteString(contextPrefix) - if i+1 >= len(offsets) { - sb.WriteString(statement[offset:]) - } else { - left = offset - } + left = offset } + sb.WriteString(statement[left:]) - return sb.String() + return sb.String(), nil } // newParser returns a parser that can be used to read a string into a parsedStatement. An error will be returned if the string diff --git a/pkg/ottl/parser_test.go b/pkg/ottl/parser_test.go index 4105f7326537..9e2e09a10e5f 100644 --- a/pkg/ottl/parser_test.go +++ b/pkg/ottl/parser_test.go @@ -2715,7 +2715,7 @@ func Test_ConditionSequence_Eval_Error(t *testing.T) { } } -func Test_appendStatementPathsContext_InvalidStatement(t *testing.T) { +func Test_prependContextToStatementPaths_InvalidStatement(t *testing.T) { ps, err := NewParser( CreateFactoryMap[any](), testParsePath[any], @@ -2724,11 +2724,11 @@ func Test_appendStatementPathsContext_InvalidStatement(t *testing.T) { WithPathContextNames[any]([]string{"foo", "bar"}), ) require.NoError(t, err) - _, err = ps.appendStatementPathsContext("foo", "this is invalid") + _, err = ps.prependContextToStatementPaths("foo", "this is invalid") require.ErrorContains(t, err, `statement has invalid syntax`) } -func Test_appendStatementPathsContext_InvalidContext(t *testing.T) { +func Test_prependContextToStatementPaths_InvalidContext(t *testing.T) { ps, err := NewParser( CreateFactoryMap[any](), testParsePath[any], @@ -2737,11 +2737,11 @@ func Test_appendStatementPathsContext_InvalidContext(t *testing.T) { WithPathContextNames[any]([]string{"foo", "bar"}), ) require.NoError(t, err) - _, err = ps.appendStatementPathsContext("foobar", "set(foo, 1)") + _, err = ps.prependContextToStatementPaths("foobar", "set(foo, 1)") require.ErrorContains(t, err, `unknown context "foobar" for parser`) } -func Test_appendStatementPathsContext_Success(t *testing.T) { +func Test_prependContextToStatementPaths_Success(t *testing.T) { type mockSetArguments[K any] struct { Target Setter[K] Value Getter[K] @@ -2845,7 +2845,7 @@ func Test_appendStatementPathsContext_Success(t *testing.T) { require.NoError(t, err) require.NotNil(t, ps) - result, err := ps.appendStatementPathsContext(tt.context, tt.statement) + result, err := ps.prependContextToStatementPaths(tt.context, tt.statement) require.NoError(t, err) assert.Equal(t, tt.expected, result) })