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]: Add ByteSliceLikeGetter interface #33536

Merged
merged 6 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
27 changes: 27 additions & 0 deletions .chloggen/add-byteslicelikegetter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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 ByteSliceLikeGetter interface

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [31929]

# (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:

# 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]
1 change: 1 addition & 0 deletions pkg/ottl/LANGUAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The following types are supported for single-value parameters in OTTL functions:
- `IntLikeGetter`
- `BoolGetter`
- `BoolLikeGetter`
- `ByteSliceLikeGetter`
- `Enum`
- `string`
- `float64`
Expand Down
84 changes: 84 additions & 0 deletions pkg/ottl/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"encoding/hex"
"fmt"
"math"
"reflect"
"strconv"
"time"
Expand Down Expand Up @@ -549,6 +550,89 @@ func (g StandardIntLikeGetter[K]) Get(ctx context.Context, tCtx K) (*int64, erro
return &result, nil
}

// ByteSliceLikeGetter is a Getter that returns []byte by converting the underlying value to an []byte if necessary
type ByteSliceLikeGetter[K any] interface {
// Get retrieves []byte value.
// The expectation is that the underlying value is converted to []byte if possible.
// If the value cannot be converted to []byte, nil and an error are returned.
// If the value is nil, nil is returned without an error.
Get(ctx context.Context, tCtx K) (byteSlice, error)
}

type StandardByteSliceLikeGetter[K any] struct {
Getter func(ctx context.Context, tCtx K) (any, error)
}

//nolint:revive
func (g StandardByteSliceLikeGetter[K]) Get(ctx context.Context, tCtx K) (byteSlice, error) {
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
val, err := g.Getter(ctx, tCtx)
if err != nil {
return nil, fmt.Errorf("error getting value in %T: %w", g, err)
}
if val == nil {
return nil, nil
}
var result byteSlice
switch v := val.(type) {
evan-bradley marked this conversation as resolved.
Show resolved Hide resolved
case byteSlice:
result = v
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
case []byte:
result = v
case string:
result = []byte(v)
case float64:
result = float64ToBytes(v)
case int64:
result = int64ToBytes(v)
case bool:
if v {
result = []byte("1")
} else {
result = []byte("0")
}
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
case pcommon.Value:
switch v.Type() {
case pcommon.ValueTypeBytes:
result = v.Bytes().AsRaw()
case pcommon.ValueTypeInt:
result = int64ToBytes(v.Int())
case pcommon.ValueTypeDouble:
result = float64ToBytes(v.Double())
case pcommon.ValueTypeStr:
result = []byte(v.Str())
case pcommon.ValueTypeBool:
if v.Bool() {
result = []byte("1")
} else {
result = []byte("0")
}
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
default:
return nil, TypeError(fmt.Sprintf("unsupported value type: %v", v.Type()))
}
default:
return nil, TypeError(fmt.Sprintf("unsupported type: %T", v))
}
return result, nil
}

func float64ToBytes(f float64) []byte {
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
bits := math.Float64bits(f)
bytes := make([]byte, 8)
for i := 0; i < 8; i++ {
bytes[i] = byte(bits >> (56 - 8*i))
}
return bytes
}

func int64ToBytes(i int64) []byte {
b := make([]byte, 8)
for j := 7; j >= 0; j-- {
b[j] = byte(i & 0xff)
i >>= 8
}
return b
}

// BoolLikeGetter is a Getter that returns a bool by converting the underlying value to a bool if necessary.
type BoolLikeGetter[K any] interface {
// Get retrieves a bool value.
Expand Down
209 changes: 209 additions & 0 deletions pkg/ottl/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,215 @@ func Test_StandardIntLikeGetter_WrappedError(t *testing.T) {
assert.False(t, ok)
}

func Test_StandardByteSliceLikeGetter(t *testing.T) {
tests := []struct {
name string
getter ByteSliceLikeGetter[any]
want any
valid bool
expectedErrorMsg string
}{
{
name: "string type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return "1", nil
},
},
want: byteSlice("1"),
valid: true,
},
{
name: "byte type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return []byte("1"), nil
},
},
want: byteSlice("1"),
valid: true,
},
{
name: "byteSlice type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return byteSlice("1"), nil
},
},
want: byteSlice("1"),
valid: true,
},
{
name: "int64 type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return int64(12), nil
},
},
want: byteSlice(int64ToBytes(12)),
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
valid: true,
},
{
name: "float64 type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return 1.1, nil
},
},
want: byteSlice(float64ToBytes(1.1)),
valid: true,
},
{
name: "primitive bool true",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return true, nil
},
},
want: byteSlice("1"),
valid: true,
},
{
name: "primitive bool false",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return false, nil
},
},
want: byteSlice("0"),
valid: true,
},
{
name: "pcommon.value type int",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueInt(int64(100))
return v, nil
},
},
want: byteSlice(int64ToBytes(100)),
valid: true,
},
{
name: "pcommon.value type float",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueDouble(float64(1.9))
return v, nil
},
},
want: byteSlice(float64ToBytes(1.9)),
valid: true,
},
{
name: "pcommon.value type string",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueStr("12")
return v, nil
},
},
want: byteSlice("12"),
valid: true,
},
{
name: "pcommon.value type bytes",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueBytes()
v.SetEmptyBytes().Append(byte(12))
return v, nil
},
},
want: byteSlice{12},
valid: true,
},
{
name: "pcommon.value type bool true",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueBool(true)
return v, nil
},
},
want: byteSlice("1"),
valid: true,
},
{
name: "pcommon.value type bool false",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueBool(false)
return v, nil
},
},
want: byteSlice("0"),
valid: true,
},
{
name: "nil",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return nil, nil
},
},
want: nil,
valid: true,
},
{
name: "invalid type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return map[string]string{}, nil
},
},
valid: false,
expectedErrorMsg: "unsupported type: map[string]string",
},
{
name: "invalid pcommon.Value type",
getter: StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
v := pcommon.NewValueMap()
return v, nil
},
},
valid: false,
expectedErrorMsg: "unsupported value type: Map",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val, err := tt.getter.Get(context.Background(), nil)
if tt.valid {
assert.NoError(t, err)
if tt.want == nil {
assert.Nil(t, val)
} else {
assert.Equal(t, tt.want, val)
}
} else {
assert.IsType(t, TypeError(""), err)
assert.EqualError(t, err, tt.expectedErrorMsg)
}
})
}
}

// nolint:errorlint
func Test_StandardByteSliceLikeGetter_WrappedError(t *testing.T) {
getter := StandardByteSliceLikeGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return nil, TypeError("")
},
}
_, err := getter.Get(context.Background(), nil)
assert.Error(t, err)
_, ok := err.(TypeError)
assert.False(t, ok)
}

func Test_StandardBoolGetter(t *testing.T) {
tests := []struct {
name string
Expand Down
6 changes: 6 additions & 0 deletions pkg/ottl/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ func (p *Parser[K]) buildArg(argVal value, argType reflect.Type) (any, error) {
return nil, err
}
return StandardBoolLikeGetter[K]{Getter: arg.Get}, nil
case strings.HasPrefix(name, "ByteSliceLikeGetter"):
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
arg, err := p.newGetter(argVal)
if err != nil {
return nil, err
}
return StandardByteSliceLikeGetter[K]{Getter: arg.Get}, nil
case name == "Enum":
arg, err := p.enumParser((*EnumSymbol)(argVal.Enum))
if err != nil {
Expand Down
Loading