diff --git a/pkg/ottl/README.md b/pkg/ottl/README.md index 20de92defd4d..8b8cc70b44dd 100644 --- a/pkg/ottl/README.md +++ b/pkg/ottl/README.md @@ -8,32 +8,64 @@ The OTTL is signal agnostic; it is not aware of the type of telemetry on which i ## Grammar -The OTTL grammar includes Invocations, Values and Boolean Expressions. +The OTTL grammar includes function invocations, Values and Boolean Expressions. These parts all fit into a Statement, which is the basis of execution in the OTTL. -### Invocations +### Editors -Invocations represent a function call that transform the underlying telemetry payload. Invocations are made up of 2 parts: +Editors are functions that transform the underlying telemetry payload. They may return a value, but typically do not. There must be a single Editor Invocation in each OTTL statement. + +An Editor is made up of 2 parts: - a string identifier. The string identifier must start with a lowercase letter. - zero or more Values (comma separated) surrounded by parentheses (`()`). -**The OTTL does not define any function implementations.** -Users must supply a map between string identifiers and the actual function implementation. -The OTTL will use this map and reflection to generate Invocations, that can then be invoked by the user. -See [ottlfuncs](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs) for pre-made, usable functions. +**The OTTL has no built-in Editors.** +Users must supply a map between string identifiers and Editor implementations. +The OTTL will use this map to determine which implementation to call when executing a Statement. +See [ottlfuncs](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#editors) for pre-made, usable Editors. + +### Converters -Example Invocations -- `route()` -- `set(field, 1)` +Converters are functions that convert data to a new format before being passed as a function argument or used in a Boolean Expression. +Converters are made up of 3 parts: -#### Invocation parameters +- a string identifier. The string identifier must start with an uppercase letter. +- zero or more Values (comma separated) surrounded by parentheses (`()`). +- a combination of zero or more a string key (`["key"]`) or int key (`[0]`) -The OTTL will use reflection to determine parameter types when parsing an invocation within a statement. +**The OTTL has no built-in Converters.** +Users must include Converters in the same map that Editors are supplied. +The OTTL will use this map and reflection to generate Converters that can then be invoked by the user. +See [ottlfuncs](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#converters) for pre-made, usable Converters. + +When keys are supplied the value returned by the Converter will be indexed by the keys in order. +If keys are supplied to a Converter and the return value cannot be indexed, or if the return value doesn't support the +type of key supplied, OTTL will error. Supported values are: + +| Type | Index Type | +|------------------|------------| +| `pcommon.Map` | `String` | +| `map[string]any` | `String` | +| `pcommon.Slice` | `Int` | +| `[]any` | `Int` | + +Example Converters +- `Int()` +- `IsMatch(field, ".*")` +- `Split(field, ",")[1]` + +### Function parameters + +The following types are supported for single-value parameters in OTTL functions: -When developing functions that represent invocations, the following types are supported for single parameter values: - `Setter` - `GetSetter` - `Getter` +- `PMapGetter` +- `FloatGetter` +- `FloatLikeGetter` +- `StringGetter` +- `StringLikeGetter` - `Enum` - `string` - `float64` @@ -41,6 +73,13 @@ When developing functions that represent invocations, the following types are su - `bool` For slice parameters, the following types are supported: + +- `Getter` +- `PMapGetter` +- `FloatGetter` +- `FloatLikeGetter` +- `StringGetter` +- `StringLikeGetter` - `string` - `float64` - `int64` @@ -49,7 +88,8 @@ For slice parameters, the following types are supported: ### Values -Values are passed as input to an Invocation or are used in a Boolean Expression. Values can take the form of: +Values are passed as function parameters or are used in a Boolean Expression. Values can take the form of: + - [Paths](#paths) - [Lists](#lists) - [Literals](#literals) @@ -57,7 +97,7 @@ Values are passed as input to an Invocation or are used in a Boolean Expression. - [Converters](#converters) - [Math Expressions](#math-expressions) -#### Paths +### Paths A Path Value is a reference to a telemetry field. Paths are made up of lowercase identifiers, dots (`.`), and square brackets combined with a string key (`["key"]`) or int key (`[0]`). **The interpretation of a Path is NOT implemented by the OTTL.** Instead, the user must provide a `PathExpressionParser` that the OTTL can use to interpret paths. As a result, how the Path parts are used is up to the user. However, it is recommended that the parts be used like so: @@ -76,7 +116,7 @@ Example Paths - `attributes["nested"]["values"]` - `cache["slice"][1]` -##### Contexts +#### Contexts The package that handles the interpretation of a path is normally called a Context. Contexts will have an implementation of `PathExpressionParser` that decides how an OTTL Path is interpreted. @@ -85,7 +125,7 @@ The context's implementation will need to make decisions like what a dot (`.`) r [There are OpenTelemetry-specific contexts provided for each signal here.](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/contexts) When using OTTL it is recommended to use these contexts unless you have a specific need. Check out each context to view the paths it supports. -#### Lists +### Lists A List Value comprises a sequence of Values. Currently, list can only be created by the grammar to be used in functions or conditions; @@ -97,7 +137,7 @@ Example List Values: - `["1", "2", "3"]` - `["a", attributes["key"], Concat(["a", "b"], "-")]` -#### Literals +### Literals Literals are literal interpretations of the Value into a Go value. Accepted literals are: @@ -116,51 +156,20 @@ Example Literals - `nil`, - `0x0001` -#### Enums +### Enums Enums are uppercase identifiers that get interpreted during parsing and converted to an `int64`. **The interpretation of an Enum is NOT implemented by the OTTL.** Instead, the user must provide a `EnumParser` that the OTTL can use to interpret the Enum. The `EnumParser` returns an `int64` instead of a function, which means that the Enum's numeric value is retrieved during parsing instead of during execution. Within the grammar Enums are always used as `int64`. As a result, the Enum's symbol can be used as if it is an Int value. -When defining a function that will be used as an Invocation by the OTTL, if the function needs to take an Enum then the function must use the `Enum` type for that argument, not an `int64`. +When defining an OTTL function, if the function needs to take an Enum then the function must use the `Enum` type for that argument, not an `int64`. -#### Converters - -Converters are special functions that convert data to a new format before being passed to an Invocation or Boolean Expression. -Converters are made up of 3 parts: - -- a string identifier. The string identifier must start with an uppercase letter. -- zero or more Values (comma separated) surrounded by parentheses (`()`). -- a combination of zero or more a string key (`["key"]`) or int key (`[0]`) - -**The OTTL does not define any Converter implementations.** -Users must include Converters in the same map that invocations are supplied. -The OTTL will use this map and reflection to generate Converters that can then be invoked by the user. -See [ottlfuncs](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#converters) for pre-made, usable Converters. - -When keys are supplied the value returned by the Converter will be indexed by the keys in order. -If keys are supplied to a Converter and the return value cannot be indexed, or if the return value doesn't support the -type of key supplied, OTTL will error. Supported values are: - -| Type | Index Type | -|------------------|------------| -| `pcommon.Map` | `String` | -| `map[string]any` | `String` | -| `pcommon.Slice` | `Int` | -| `[]any` | `Int` | - -Example Converters -- `Int()` -- `IsMatch(field, ".*")` -- `Split(field, ",")[1]` - - -#### Math Expressions +### Math Expressions Math Expressions represent arithmetic calculations. They support `+`, `-`, `*`, and `/`, along with `()` for grouping. Math Expressions currently only support `int64` and `float64`. -Math Expressions support `Paths` and `Invocations` that return supported types. +Math Expressions support `Paths` and `Editors` that return supported types. Note that `*` and `/` take precedence over `+` and `-`. Operations that share the same level of precedence will be executed in the order that they appear in the Math Expression. Math Expressions can be grouped with parentheses to override evaluation precedence. @@ -169,7 +178,7 @@ It is up to the function using the Math Expression to determine what to do with Division by zero is gracefully handled with an error, but other arithmetic operations that would result in a panic will still result in a panic. Division of integers results in an integer and follows Go's rules for division of integers. -Since Math Expressions support `Path` and `Invocation`, they are evaluated during data processing. +Since Math Expressions support `Path`s and `Converter`s as input, they are evaluated during data processing. __As a result, in order for a function to be able to accept an Math Expressions as a parameter it must use a `Getter`.__ Example Math Expressions @@ -180,7 +189,7 @@ Example Math Expressions ### Boolean Expressions -Boolean Expressions allow a decision to be made about whether an Invocation should be called. Boolean Expressions are optional. When used, the parsed statement will include a `Condition`, which can be used to evaluate the result of the statement's Boolean Expression. Boolean Expressions always evaluate to a boolean value (true or false). +Boolean Expressions allow a decision to be made about whether an Editor should be called. Boolean Expressions are optional. When used, the parsed statement will include a `Condition`, which can be used to evaluate the result of the statement's Boolean Expression. Boolean Expressions always evaluate to a boolean value (true or false). Boolean Expressions consist of the literal string `where` followed by one or more Booleans (see below). Booleans can be joined with the literal strings `and` and `or`. @@ -211,7 +220,7 @@ Booleans can be negated with the `not` keyword such as - `not name == "foo"` - `not (IsMatch(name, "http_.*") and kind > 0)` -### Comparison Rules +## Comparison Rules The table below describes what happens when two Values are compared. Value types are provided by the user of OTTL. All of the value types supported by OTTL are listed in this table. @@ -246,10 +255,11 @@ Access to signal telemetry is provided to OTTL functions through a `TransformCon ### Getters and Setters Getters allow for reading the following types of data. See the respective section of each Value type for how they are interpreted. + - [Paths](#paths). - [Enums](#enums). - [Literals](#literals). -- [Invocations](#invocations). +- [Converters](#converters). It is possible to update the Value in a telemetry field using a Setter. For read and write access, the `GetSetter` interface extends both interfaces. diff --git a/pkg/ottl/expression.go b/pkg/ottl/expression.go index 5fdcdf703c31..2c78ecdebc10 100644 --- a/pkg/ottl/expression.go +++ b/pkg/ottl/expression.go @@ -352,7 +352,7 @@ func (p *Parser[K]) newGetter(val value) (Getter[K], error) { } func (p *Parser[K]) newGetterFromConverter(c converter) (Getter[K], error) { - call, err := p.newFunctionCall(invocation(c)) + call, err := p.newFunctionCall(editor(c)) if err != nil { return nil, err } diff --git a/pkg/ottl/factory.go b/pkg/ottl/factory.go index 77678a0f1b97..4fbb1b9704c5 100644 --- a/pkg/ottl/factory.go +++ b/pkg/ottl/factory.go @@ -27,7 +27,7 @@ type FunctionContext struct { } // Factory defines an OTTL function factory that will generate an OTTL -// function to be called based on an invocation within a statement. +// function to be called 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. diff --git a/pkg/ottl/functions.go b/pkg/ottl/functions.go index 0290ebd30348..c5d6667554fb 100644 --- a/pkg/ottl/functions.go +++ b/pkg/ottl/functions.go @@ -30,10 +30,10 @@ type EnumParser func(*EnumSymbol) (*Enum, error) type Enum int64 -func (p *Parser[K]) newFunctionCall(inv invocation) (Expr[K], error) { - f, ok := p.functions[inv.Function] +func (p *Parser[K]) newFunctionCall(ed editor) (Expr[K], error) { + f, ok := p.functions[ed.Function] if !ok { - return Expr[K]{}, fmt.Errorf("undefined function %v", inv.Function) + return Expr[K]{}, fmt.Errorf("undefined function %v", ed.Function) } args := f.CreateDefaultArguments() @@ -43,12 +43,12 @@ func (p *Parser[K]) newFunctionCall(inv invocation) (Expr[K], error) { // 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) + return Expr[K]{}, fmt.Errorf("factory for %s must return a pointer to an Arguments value in its CreateDefaultArguments method", ed.Function) } - err := p.buildArgs(inv, reflect.ValueOf(args).Elem()) + err := p.buildArgs(ed, reflect.ValueOf(args).Elem()) 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("error while parsing arguments for call to '%v': %w", ed.Function, err) } } @@ -60,9 +60,9 @@ func (p *Parser[K]) newFunctionCall(inv invocation) (Expr[K], error) { return Expr[K]{exprFunc: fn}, err } -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)) +func (p *Parser[K]) buildArgs(ed editor, argsVal reflect.Value) error { + if len(ed.Arguments) != argsVal.NumField() { + return fmt.Errorf("incorrect number of arguments. Expected: %d Received: %d", argsVal.NumField(), len(ed.Arguments)) } argsType := argsVal.Type() @@ -83,11 +83,11 @@ func (p *Parser[K]) buildArgs(inv invocation, argsVal reflect.Value) error { return fmt.Errorf("ottlarg struct tag on field '%s' is not a valid integer: %w", argsType.Field(i).Name, err) } - 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)) + if argNum < 0 || argNum >= len(ed.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(ed.Arguments)) } - argVal := inv.Arguments[argNum] + argVal := ed.Arguments[argNum] var val any if fieldType.Kind() == reflect.Slice { @@ -172,7 +172,7 @@ func (p *Parser[K]) buildSliceArg(argVal value, argType reflect.Type) (any, erro } } -// Handle interfaces that can be passed as arguments to OTTL function invocations. +// Handle interfaces that can be passed as arguments to OTTL functions. func (p *Parser[K]) buildArg(argVal value, argType reflect.Type) (any, error) { name := argType.Name() switch { diff --git a/pkg/ottl/functions_test.go b/pkg/ottl/functions_test.go index c99722e2df9c..6a802bf89df8 100644 --- a/pkg/ottl/functions_test.go +++ b/pkg/ottl/functions_test.go @@ -110,18 +110,18 @@ func Test_NewFunctionCall_invalid(t *testing.T) { tests := []struct { name string - inv invocation + inv editor }{ { name: "unknown function", - inv: invocation{ + inv: editor{ Function: "unknownfunc", Arguments: []value{}, }, }, { name: "not accessor", - inv: invocation{ + inv: editor{ Function: "testing_getsetter", Arguments: []value{ { @@ -132,7 +132,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "not reader (invalid function)", - inv: invocation{ + inv: editor{ Function: "testing_getter", Arguments: []value{ { @@ -147,7 +147,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "not enough args", - inv: invocation{ + inv: editor{ Function: "testing_multiple_args", Arguments: []value{ { @@ -169,7 +169,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "too many args", - inv: invocation{ + inv: editor{ Function: "testing_multiple_args", Arguments: []value{ { @@ -194,7 +194,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "not enough args with telemetrySettings", - inv: invocation{ + inv: editor{ Function: "testing_telemetry_settings_first", Arguments: []value{ { @@ -214,7 +214,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "too many args with telemetrySettings", - inv: invocation{ + inv: editor{ Function: "testing_telemetry_settings_first", Arguments: []value{ { @@ -244,7 +244,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "not matching arg type", - inv: invocation{ + inv: editor{ Function: "testing_string", Arguments: []value{ { @@ -257,7 +257,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "not matching arg type when byte slice", - inv: invocation{ + inv: editor{ Function: "testing_byte_slice", Arguments: []value{ { @@ -274,7 +274,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "mismatching slice element type", - inv: invocation{ + inv: editor{ Function: "testing_string_slice", Arguments: []value{ { @@ -296,7 +296,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "mismatching slice argument type", - inv: invocation{ + inv: editor{ Function: "testing_string_slice", Arguments: []value{ { @@ -307,13 +307,13 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "function call returns error", - inv: invocation{ + inv: editor{ Function: "testing_error", }, }, { name: "Enum not found", - inv: invocation{ + inv: editor{ Function: "testing_enum", Arguments: []value{ { @@ -324,13 +324,13 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "factory definition uses a non-pointer Arguments value", - inv: invocation{ + inv: editor{ Function: "non_pointer", }, }, { name: "no struct tags", - inv: invocation{ + inv: editor{ Function: "no_struct_tag", Arguments: []value{ { @@ -341,7 +341,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "using the wrong struct tag", - inv: invocation{ + inv: editor{ Function: "wrong_struct_tag", Arguments: []value{ { @@ -352,7 +352,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "non-integer struct tags", - inv: invocation{ + inv: editor{ Function: "bad_struct_tag", Arguments: []value{ { @@ -363,7 +363,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "struct tag index too low", - inv: invocation{ + inv: editor{ Function: "negative_struct_tag", Arguments: []value{ { @@ -374,7 +374,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) { }, { name: "struct tag index too high", - inv: invocation{ + inv: editor{ Function: "out_of_bounds_struct_tag", Arguments: []value{ { @@ -404,12 +404,12 @@ func Test_NewFunctionCall(t *testing.T) { tests := []struct { name string - inv invocation + inv editor want any }{ { name: "no arguments", - inv: invocation{ + inv: editor{ Function: "testing_noop", Arguments: []value{ { @@ -423,7 +423,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "empty slice arg", - inv: invocation{ + inv: editor{ Function: "testing_string_slice", Arguments: []value{ { @@ -437,7 +437,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "string slice arg", - inv: invocation{ + inv: editor{ Function: "testing_string_slice", Arguments: []value{ { @@ -461,7 +461,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "float slice arg", - inv: invocation{ + inv: editor{ Function: "testing_float_slice", Arguments: []value{ { @@ -491,7 +491,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "int slice arg", - inv: invocation{ + inv: editor{ Function: "testing_int_slice", Arguments: []value{ { @@ -521,7 +521,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "getter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_getter_slice", Arguments: []value{ { @@ -628,7 +628,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "stringgetter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_stringgetter_slice", Arguments: []value{ { @@ -649,7 +649,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "floatgetter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_floatgetter_slice", Arguments: []value{ { @@ -672,7 +672,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "pmapgetter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_pmapgetter_slice", Arguments: []value{ { @@ -709,7 +709,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "stringlikegetter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_stringlikegetter_slice", Arguments: []value{ { @@ -732,7 +732,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "floatlikegetter slice arg", - inv: invocation{ + inv: editor{ Function: "testing_floatlikegetter_slice", Arguments: []value{ { @@ -755,7 +755,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "setter arg", - inv: invocation{ + inv: editor{ Function: "testing_setter", Arguments: []value{ { @@ -775,7 +775,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "getsetter arg", - inv: invocation{ + inv: editor{ Function: "testing_getsetter", Arguments: []value{ { @@ -795,7 +795,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "getter arg", - inv: invocation{ + inv: editor{ Function: "testing_getter", Arguments: []value{ { @@ -815,7 +815,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "getter arg with nil literal", - inv: invocation{ + inv: editor{ Function: "testing_getter", Arguments: []value{ { @@ -827,7 +827,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "getter arg with list", - inv: invocation{ + inv: editor{ Function: "testing_getter", Arguments: []value{ { @@ -892,7 +892,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "stringgetter arg", - inv: invocation{ + inv: editor{ Function: "testing_stringgetter", Arguments: []value{ { @@ -904,7 +904,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "stringlikegetter arg", - inv: invocation{ + inv: editor{ Function: "testing_stringlikegetter", Arguments: []value{ { @@ -916,7 +916,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "floatgetter arg", - inv: invocation{ + inv: editor{ Function: "testing_floatgetter", Arguments: []value{ { @@ -928,7 +928,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "floatlikegetter arg", - inv: invocation{ + inv: editor{ Function: "testing_floatlikegetter", Arguments: []value{ { @@ -940,7 +940,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "intgetter arg", - inv: invocation{ + inv: editor{ Function: "testing_intgetter", Arguments: []value{ { @@ -954,7 +954,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "pmapgetter arg", - inv: invocation{ + inv: editor{ Function: "testing_pmapgetter", Arguments: []value{ { @@ -974,7 +974,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "string arg", - inv: invocation{ + inv: editor{ Function: "testing_string", Arguments: []value{ { @@ -986,7 +986,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "float arg", - inv: invocation{ + inv: editor{ Function: "testing_float", Arguments: []value{ { @@ -1000,7 +1000,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "int arg", - inv: invocation{ + inv: editor{ Function: "testing_int", Arguments: []value{ { @@ -1014,7 +1014,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "bool arg", - inv: invocation{ + inv: editor{ Function: "testing_bool", Arguments: []value{ { @@ -1026,7 +1026,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "byteSlice arg", - inv: invocation{ + inv: editor{ Function: "testing_byte_slice", Arguments: []value{ { @@ -1038,7 +1038,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "multiple args", - inv: invocation{ + inv: editor{ Function: "testing_multiple_args", Arguments: []value{ { @@ -1071,7 +1071,7 @@ func Test_NewFunctionCall(t *testing.T) { }, { name: "Enum arg", - inv: invocation{ + inv: editor{ Function: "testing_enum", Arguments: []value{ { diff --git a/pkg/ottl/grammar.go b/pkg/ottl/grammar.go index 36357a3e6d00..eee34aaa5eeb 100644 --- a/pkg/ottl/grammar.go +++ b/pkg/ottl/grammar.go @@ -23,7 +23,7 @@ import ( // parsedStatement represents a parsed statement. It is the entry point into the statement DSL. type parsedStatement struct { - Invocation invocation `parser:"(@@"` + Editor editor `parser:"(@@"` // If converter is matched then return error Converter *converter `parser:"|@@)"` WhereClause *booleanExpression `parser:"( 'where' @@ )?"` @@ -31,9 +31,9 @@ type parsedStatement struct { func (p *parsedStatement) checkForCustomError() error { if p.Converter != nil { - return fmt.Errorf("invocation names must start with a lowercase letter but got '%v'", p.Converter.Function) + return fmt.Errorf("editor names must start with a lowercase letter but got '%v'", p.Converter.Function) } - err := p.Invocation.checkForCustomError() + err := p.Editor.checkForCustomError() if err != nil { return err } @@ -198,15 +198,15 @@ func (c *comparison) checkForCustomError() error { return err } -// invocation represents the function call of a statement. -type invocation struct { +// editor represents the function call of a statement. +type editor struct { Function string `parser:"@(Lowercase(Uppercase | Lowercase)*)"` Arguments []value `parser:"'(' ( @@ ( ',' @@ )* )? ')'"` // If keys are matched return an error Keys []Key `parser:"( @@ )*"` } -func (i *invocation) checkForCustomError() error { +func (i *editor) checkForCustomError() error { var err error for _, arg := range i.Arguments { err = arg.checkForCustomError() @@ -215,7 +215,7 @@ func (i *invocation) checkForCustomError() error { } } if i.Keys != nil { - return fmt.Errorf("only paths and converters may be indexed, not invocations, but got %v %v", i.Function, i.Keys) + return fmt.Errorf("only paths and converters may be indexed, not editors, but got %v %v", i.Function, i.Keys) } return nil } @@ -300,17 +300,17 @@ func (n *isNil) Capture(_ []string) error { } type mathExprLiteral struct { - // If invocation is matched then error - Invocation *invocation `parser:"( @@"` - Converter *converter `parser:"| @@"` - Float *float64 `parser:"| @Float"` - Int *int64 `parser:"| @Int"` - Path *Path `parser:"| @@ )"` + // If editor is matched then error + Editor *editor `parser:"( @@"` + Converter *converter `parser:"| @@"` + Float *float64 `parser:"| @Float"` + Int *int64 `parser:"| @Int"` + Path *Path `parser:"| @@ )"` } func (m *mathExprLiteral) checkForCustomError() error { - if m.Invocation != nil { - return fmt.Errorf("converter names must start with an uppercase letter but got '%v'", m.Invocation.Function) + if m.Editor != nil { + return fmt.Errorf("converter names must start with an uppercase letter but got '%v'", m.Editor.Function) } return nil } diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index fede8a666cb2..8860fe098823 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -20,16 +20,17 @@ See the component-specific guides for how each uses error mode: - [routingprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/routingprocessor#tech-preview-opentelemetry-transformation-language-statements-as-routing-conditions) - [transformprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor#config) -## Functions +## Editors -Functions are what OTTL uses to transform telemetry. +Editors are what OTTL uses to transform telemetry. + +Editors: -Functions: - Are allowed to transform telemetry. When a Function is invoked the expectation is that the underlying telemetry is modified in some way. - May have side effects. Some Functions may generate telemetry and add it to the telemetry payload to be processed in this batch. - May return values. Although not common and not required, Functions may return values. -Available Functions: +Available Editors: - [delete_key](#delete_key) - [delete_matching_keys](#delete_matching_keys) - [keep_keys](#keep_keys) diff --git a/pkg/ottl/parser.go b/pkg/ottl/parser.go index 54bf827bb74f..6fd82aaa7d83 100644 --- a/pkg/ottl/parser.go +++ b/pkg/ottl/parser.go @@ -124,7 +124,7 @@ func (p *Parser[K]) ParseStatement(statement string) (*Statement[K], error) { if err != nil { return nil, err } - function, err := p.newFunctionCall(parsed.Invocation) + function, err := p.newFunctionCall(parsed.Editor) if err != nil { return nil, err } diff --git a/pkg/ottl/parser_test.go b/pkg/ottl/parser_test.go index fd08c3a4b69f..338cc7d22f50 100644 --- a/pkg/ottl/parser_test.go +++ b/pkg/ottl/parser_test.go @@ -39,10 +39,10 @@ func Test_parse(t *testing.T) { expected *parsedStatement }{ { - name: "invocation with string", + name: "editor with string", statement: `set("foo")`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -54,10 +54,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with float", + name: "editor with float", statement: `met(1.2)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "met", Arguments: []value{ { @@ -71,10 +71,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with int", + name: "editor with int", statement: `fff(12)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "fff", Arguments: []value{ { @@ -88,10 +88,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "complex invocation", + name: "complex editor", statement: `set("foo", GetSomething(bear.honey))`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -129,7 +129,7 @@ func Test_parse(t *testing.T) { name: "complex path", statement: `set(foo.attributes["bar"].cat, "dog")`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -166,7 +166,7 @@ func Test_parse(t *testing.T) { name: "complex path", statement: `set(foo.bar["x"]["y"].z, Test()[0]["pass"])`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -218,7 +218,7 @@ func Test_parse(t *testing.T) { name: "where == clause", statement: `set(foo.attributes["bar"].cat, "dog") where name == "fido"`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -277,7 +277,7 @@ func Test_parse(t *testing.T) { name: "where != clause", statement: `set(foo.attributes["bar"].cat, "dog") where name != "fido"`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -336,7 +336,7 @@ func Test_parse(t *testing.T) { name: "ignore extra spaces", statement: `set ( foo.attributes[ "bar"].cat, "dog") where name=="fido"`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -395,7 +395,7 @@ func Test_parse(t *testing.T) { name: "handle quotes", statement: `set("fo\"o")`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -407,10 +407,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with boolean false", + name: "editor with boolean false", statement: `convert_gauge_to_sum("cumulative", false)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "convert_gauge_to_sum", Arguments: []value{ { @@ -425,10 +425,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with boolean true", + name: "editor with boolean true", statement: `convert_gauge_to_sum("cumulative", true)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "convert_gauge_to_sum", Arguments: []value{ { @@ -443,10 +443,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with bytes", + name: "editor with bytes", statement: `set(attributes["bytes"], 0x0102030405060708)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -474,10 +474,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with nil", + name: "editor with nil", statement: `set(attributes["test"], nil)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -505,10 +505,10 @@ func Test_parse(t *testing.T) { }, }, { - name: "invocation with Enum", + name: "editor with Enum", statement: `set(attributes["test"], TEST_ENUM)`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -539,7 +539,7 @@ func Test_parse(t *testing.T) { name: "Converter with empty list", statement: `set(attributes["test"], [])`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -572,7 +572,7 @@ func Test_parse(t *testing.T) { name: "Converter with single-value list", statement: `set(attributes["test"], ["value0"])`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -609,7 +609,7 @@ func Test_parse(t *testing.T) { name: "Converter with multi-value list", statement: `set(attributes["test"], ["value1", "value2"])`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -649,7 +649,7 @@ func Test_parse(t *testing.T) { name: "Converter with nested heterogeneous types", statement: `set(attributes["test"], [Concat(["a", "b"], "+"), ["1", 2, 3.0], nil, attributes["test"]])`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -745,7 +745,7 @@ func Test_parse(t *testing.T) { name: "Converter math mathExpression", statement: `set(attributes["test"], 1000 - 600) where 1 + 1 * 2 == three / One()`, expected: &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ { @@ -892,7 +892,7 @@ func testParsePath(val *Path) (GetSetter[interface{}], error) { // Parse string should start with `set(name, "test") where`... func setNameTest(b *booleanExpression) *parsedStatement { return &parsedStatement{ - Invocation: invocation{ + Editor: editor{ Function: "set", Arguments: []value{ {