Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pkg/ottl] Update functions to use StringGetter #19137

Merged
merged 6 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .chloggen/ottl-use-stringgetter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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: Updates `ConvertCase`, `ParseJSON`, `Split`, and `Substring` to use `StringGetter`

# One or more tracking issues related to the change
issues: [19137]

# (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: |
Affected components: `transformprocessor`, `filterprocessor`, `routingprocessor`. It is HIGHLY recommended to use each component's `error_mode` configuration option to handle errors returned by these functions.
22 changes: 17 additions & 5 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# OTTL Functions

The following functions are intended to be used in implementations of the OpenTelemetry Transformation Language that interact with otel data via the collector's internal data model, [pdata](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata). These functions may make assumptions about the types of the data returned by Paths.
The following functions are intended to be used in implementations of the OpenTelemetry Transformation Language that
interact with OTel data via the Collector's internal data model, [pdata](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata).
Functions generally expect specific types to be returned by `Paths`.
For these functions, if that type is not returned or if `nil` is returned, the function will error.
Some functions are able to handle different types and will generally convert those types to their desired type.
In these situations the function will error if it does not know how to do the conversion.
Use `ErrorMode` to determine how the `Statement` handles these errors.
evan-bradley marked this conversation as resolved.
Show resolved Hide resolved
See the component-specific guides for how each uses error mode:
- [filterprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor#ottl)
- [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

Expand Down Expand Up @@ -38,7 +48,7 @@ List of available Converters:
- [ConvertCase](#convertcase)
- [Int](#int)
- [IsMatch](#ismatch)
- [ParseJSON](#ParseJSON)
- [ParseJSON](#parsejson)
- [SpanID](#spanid)
- [Split](#split)
- [TraceID](#traceid)
Expand Down Expand Up @@ -72,7 +82,7 @@ The `ConvertCase` factory function converts the `target` string into the desired

`target` is a string. `toCase` is a string.

If the `target` is not a string or does not exist, the `ConvertCase` factory function will return `nil`.
If the `target` is not a string or does not exist, the `ConvertCase` factory function will return an error.

`toCase` can be:

Expand Down Expand Up @@ -145,6 +155,7 @@ Examples:
The `ParseJSON` factory function returns a `pcommon.Map` struct that is a result of parsing the target string as JSON

`target` is a Getter that returns a string. This string should be in json format.
If `target` is not a string, nil, or cannot be parsed as JSON, `ParseJSON` will return an error.

Unmarshalling is done using [jsoniter](https://github.com/json-iterator/go).
Each JSON type is converted into a `pdata.Value` using the following map:
Expand Down Expand Up @@ -188,7 +199,7 @@ The `Split` factory function separates a string by the delimiter, and returns an

`target` is a string. `delimiter` is a string.

If the `target` is not a string or does not exist, the `Split` factory function will return `nil`.
If the `target` is not a string or does not exist, the `Split` factory function will return an error.

Examples:

Expand All @@ -214,7 +225,8 @@ The `Substring` Converter returns a substring from the given start index to the

`target` is a string. `start` and `length` are `int64`.

The `Substring` Converter will return `nil` if the given parameters are invalid, e.x. `target` is not a string, or the start/length exceed the length of the `target` string.
If `target` is not a string or is nil, an error is returned.
If the start/length exceed the length of the `target` string, an error is returned.

Examples:

Expand Down
44 changes: 19 additions & 25 deletions pkg/ottl/ottlfuncs/func_convert_case.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,46 +24,40 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

func ConvertCase[K any](target ottl.Getter[K], toCase string) (ottl.ExprFunc[K], error) {
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)
}

return func(ctx context.Context, tCtx K) (interface{}, error) {
val, err := target.Get(ctx, tCtx)

if err != nil {
return nil, err
}

if valStr, ok := val.(string); ok {

if valStr == "" {
return valStr, nil
}
if val == "" {
return val, nil
}

switch toCase {
// Convert string to lowercase (SOME_NAME -> some_name)
case "lower":
return strings.ToLower(valStr), nil
switch toCase {
// Convert string to lowercase (SOME_NAME -> some_name)
case "lower":
return strings.ToLower(val), nil

// Convert string to uppercase (some_name -> SOME_NAME)
case "upper":
return strings.ToUpper(valStr), nil
// Convert string to uppercase (some_name -> SOME_NAME)
case "upper":
return strings.ToUpper(val), nil

// Convert string to snake case (someName -> some_name)
case "snake":
return strcase.ToSnake(valStr), nil
// Convert string to snake case (someName -> some_name)
case "snake":
return strcase.ToSnake(val), nil

// Convert string to camel case (some_name -> SomeName)
case "camel":
return strcase.ToCamel(valStr), nil
// Convert string to camel case (some_name -> SomeName)
case "camel":
return strcase.ToCamel(val), nil

default:
return nil, fmt.Errorf("error handling unexpected case: %s", toCase)
}
default:
return nil, fmt.Errorf("error handling unexpected case: %s", toCase)
}

return nil, nil
}, nil
}
116 changes: 56 additions & 60 deletions pkg/ottl/ottlfuncs/func_convert_case_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ import (
func Test_convertCase(t *testing.T) {
tests := []struct {
name string
target ottl.Getter[interface{}]
target ottl.StringGetter[interface{}]
toCase string
expected interface{}
}{
// snake case
{
name: "snake simple convert",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simpleString", nil
},
Expand All @@ -44,7 +44,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "snake noop already snake case",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simple_string", nil
},
Expand All @@ -54,7 +54,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "snake multiple uppercase",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "CPUUtilizationMetric", nil
},
Expand All @@ -64,27 +64,17 @@ func Test_convertCase(t *testing.T) {
},
{
name: "snake hyphens",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simple-string", nil
},
},
toCase: "snake",
expected: "simple_string",
},
{
name: "snake nil",
target: &ottl.StandardGetSetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, nil
},
},
toCase: "snake",
expected: nil,
},
{
name: "snake empty string",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "", nil
},
Expand All @@ -95,7 +85,7 @@ func Test_convertCase(t *testing.T) {
// camel case
{
name: "camel simple convert",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simple_string", nil
},
Expand All @@ -105,7 +95,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "snake noop already snake case",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "SimpleString", nil
},
Expand All @@ -115,27 +105,17 @@ func Test_convertCase(t *testing.T) {
},
{
name: "snake hyphens",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simple-string", nil
},
},
toCase: "camel",
expected: "SimpleString",
},
{
name: "snake nil",
target: &ottl.StandardGetSetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, nil
},
},
toCase: "camel",
expected: nil,
},
{
name: "snake empty string",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "", nil
},
Expand All @@ -146,7 +126,7 @@ func Test_convertCase(t *testing.T) {
// upper case
{
name: "upper simple",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simple", nil
},
Expand All @@ -156,7 +136,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "upper complex",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "complex_SET-of.WORDS1234", nil
},
Expand All @@ -166,7 +146,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "upper empty string",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "", nil
},
Expand All @@ -177,7 +157,7 @@ func Test_convertCase(t *testing.T) {
// lower case
{
name: "lower simple",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "SIMPLE", nil
},
Expand All @@ -187,7 +167,7 @@ func Test_convertCase(t *testing.T) {
},
{
name: "lower complex",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "complex_SET-of.WORDS1234", nil
},
Expand All @@ -197,36 +177,14 @@ func Test_convertCase(t *testing.T) {
},
{
name: "lower empty string",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "", nil
},
},
toCase: "lower",
expected: "",
},
// nil test
{
name: "nil",
target: &ottl.StandardGetSetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, nil
},
},
toCase: "upper",
expected: nil,
},
// non-string test
{
name: "non-string",
target: &ottl.StandardGetSetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return 10, nil
},
},
toCase: "upper",
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -242,12 +200,12 @@ func Test_convertCase(t *testing.T) {
func Test_convertCaseError(t *testing.T) {
tests := []struct {
name string
target ottl.Getter[interface{}]
target ottl.StringGetter[interface{}]
toCase string
}{
{
name: "error bad case",
target: &ottl.StandardGetSetter[interface{}]{
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return "simpleString", nil
},
Expand All @@ -263,3 +221,41 @@ func Test_convertCaseError(t *testing.T) {
})
}
}

func Test_convertCaseRuntimeError(t *testing.T) {
tests := []struct {
name string
target ottl.StringGetter[interface{}]
toCase string
expectedError string
}{
{
name: "non-string",
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return 10, nil
},
},
toCase: "upper",
expectedError: "expected string but got int",
},
{
name: "nil",
target: &ottl.StandardTypeGetter[interface{}, string]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, nil
},
},
toCase: "snake",
expectedError: "expected string but got nil",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := ConvertCase[any](tt.target, tt.toCase)
require.NoError(t, err)
_, err = exprFunc(context.Background(), nil)
assert.ErrorContains(t, err, tt.expectedError)
})
}
}
Loading