Skip to content

Commit

Permalink
make sorting optional
Browse files Browse the repository at this point in the history
  • Loading branch information
kuiperda committed Sep 30, 2024
1 parent 0509056 commit 21b464c
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 37 deletions.
8 changes: 4 additions & 4 deletions pkg/ottl/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,25 +616,25 @@ func Test_e2e_converters(t *testing.T) {
},
},
{
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2")))`,
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2"), "=", " ", true))`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "k1=v1 k2=v2")
},
},
{
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1:v1,k2:v2", ":" , ","), ":", ","))`,
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1:v1,k2:v2", ":" , ","), ":", ",", true))`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "k1:v1,k2:v2")
},
},
{
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2"), "!", "+"))`,
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2"), "!", "+", true))`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "k1!v1+k2!v2")
},
},
{
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2=v3")))`,
statement: `set(attributes["test"], ToKeyValueString(ParseKeyValue("k1=v1 k2=v2=v3"), "=", " ", true))`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "k1=v1 k2=\"v2=v3\"")
},
Expand Down
5 changes: 3 additions & 2 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1633,13 +1633,14 @@ Examples:

### ToKeyValueString

`ToKeyValueString(target, Optional[delimiter], Optional[pair_delimiter])`
`ToKeyValueString(target, Optional[delimiter], Optional[pair_delimiter], Optional[sort_output])`

The `ToKeyValueString` Converter takes a `pcommon.Map` and converts it to a `string` of key value pairs.

- `target` is a Getter that returns a `pcommon.Map`.
- `delimiter` is an optional string that is used to join keys and values, the default is `=`.
- `pair_delimiter` is an optional string that is used to join key value pairs, the default is a single space (` `).
- `sort_output` is an optional bool that is used to deterministically sort the keys of the output string. It should only be used if the output is required to be in the same order each time, as it introduces some performance overhead.

For example, the following map `{"k1":"v1","k2":"v2","k3":"v3"}` will use default delimiters and be converted into the following string:

Expand All @@ -1666,7 +1667,7 @@ For example, `{"k1":"v1","k2":"v=2","k3"="\"v=3\""}` will be converted to:
Examples:

- `ToKeyValueString(body)`
- `ToKeyValueString(body, ":", ",")`
- `ToKeyValueString(body, ":", ",", true)`

### TraceID

Expand Down
64 changes: 40 additions & 24 deletions pkg/ottl/ottlfuncs/func_to_key_value_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ToKeyValueStringArguments[K any] struct {
Target ottl.PMapGetter[K]
Delimiter ottl.Optional[string]
PairDelimiter ottl.Optional[string]
SortOutput ottl.Optional[bool]
}

func NewToKeyValueStringFactory[K any]() ottl.Factory[K] {
Expand All @@ -31,10 +32,10 @@ func createToKeyValueStringFunction[K any](_ ottl.FunctionContext, oArgs ottl.Ar
return nil, fmt.Errorf("ToKeyValueStringFactory args must be of type *ToKeyValueStringArguments[K]")
}

return toKeyValueString[K](args.Target, args.Delimiter, args.PairDelimiter)
return toKeyValueString[K](args.Target, args.Delimiter, args.PairDelimiter, args.SortOutput)
}

func toKeyValueString[K any](target ottl.PMapGetter[K], d ottl.Optional[string], p ottl.Optional[string]) (ottl.ExprFunc[K], error) {
func toKeyValueString[K any](target ottl.PMapGetter[K], d ottl.Optional[string], p ottl.Optional[string], s ottl.Optional[bool]) (ottl.ExprFunc[K], error) {
delimiter := "="
if !d.IsEmpty() {
if d.Get() == "" {
Expand All @@ -55,43 +56,58 @@ func toKeyValueString[K any](target ottl.PMapGetter[K], d ottl.Optional[string],
return nil, fmt.Errorf("pair delimiter %q cannot be equal to delimiter %q", pairDelimiter, delimiter)
}

sortOutput := false
if !s.IsEmpty() {
sortOutput = s.Get()
}

return func(ctx context.Context, tCtx K) (any, error) {
source, err := target.Get(ctx, tCtx)
if err != nil {
return nil, err
}

kvString := convertMapToKV(source, delimiter, pairDelimiter)
kvString := convertMapToKV(source, delimiter, pairDelimiter, sortOutput)

return kvString, nil
}, nil
}

// convertMapToKV converts a pcommon.Map to a key value string
func convertMapToKV(target pcommon.Map, delimiter string, pairDelimiter string) string {
var kvStrings []string
var keyValues []struct {
key string
val pcommon.Value
}
func convertMapToKV(target pcommon.Map, delimiter string, pairDelimiter string, sortOutput bool) string {

// Sort by keys
target.Range(func(k string, v pcommon.Value) bool {
keyValues = append(keyValues, struct {
var kvStrings []string
if sortOutput {
var keyValues []struct {
key string
val pcommon.Value
}{key: k, val: v})
return true
})
gosort.Slice(keyValues, func(i, j int) bool {
return keyValues[i].key < keyValues[j].key
})

// Convert KV pairs
for _, kv := range keyValues {
k := escapeAndQuoteKV(kv.key, delimiter, pairDelimiter)
vStr := escapeAndQuoteKV(kv.val.AsString(), delimiter, pairDelimiter)
kvStrings = append(kvStrings, fmt.Sprintf("%s%s%v", k, delimiter, vStr))
}

// Sort by keys
target.Range(func(k string, v pcommon.Value) bool {
keyValues = append(keyValues, struct {
key string
val pcommon.Value
}{key: k, val: v})
return true
})
gosort.Slice(keyValues, func(i, j int) bool {
return keyValues[i].key < keyValues[j].key
})

// Convert KV pairs
for _, kv := range keyValues {
k := escapeAndQuoteKV(kv.key, delimiter, pairDelimiter)
vStr := escapeAndQuoteKV(kv.val.AsString(), delimiter, pairDelimiter)
kvStrings = append(kvStrings, fmt.Sprintf("%s%s%v", k, delimiter, vStr))
}
} else {
target.Range(func(k string, v pcommon.Value) bool {
key := escapeAndQuoteKV(k, delimiter, pairDelimiter)
vStr := escapeAndQuoteKV(v.AsString(), delimiter, pairDelimiter)
kvStrings = append(kvStrings, fmt.Sprintf("%s%s%v", key, delimiter, vStr))
return true
})
}

return strings.Join(kvStrings, pairDelimiter)
Expand Down
14 changes: 7 additions & 7 deletions pkg/ottl/ottlfuncs/func_to_key_value_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func Test_toKeyValueString(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := toKeyValueString[any](tt.target, tt.delimiter, tt.pairDelimiter)
exprFunc, err := toKeyValueString[any](tt.target, tt.delimiter, tt.pairDelimiter, ottl.NewTestingOptional[bool](true))
assert.NoError(t, err)

result, err := exprFunc(context.Background(), nil)
Expand All @@ -197,11 +197,11 @@ func Test_toKeyValueString_equal_delimiters(t *testing.T) {
}
delimiter := ottl.NewTestingOptional[string]("=")
pairDelimiter := ottl.NewTestingOptional[string]("=")
_, err := toKeyValueString[any](target, delimiter, pairDelimiter)
_, err := toKeyValueString[any](target, delimiter, pairDelimiter, ottl.NewTestingOptional[bool](false))
assert.Error(t, err)

delimiter = ottl.NewTestingOptional[string](" ")
_, err = toKeyValueString[any](target, delimiter, ottl.Optional[string]{})
_, err = toKeyValueString[any](target, delimiter, ottl.Optional[string]{}, ottl.NewTestingOptional[bool](false))
assert.Error(t, err)
}

Expand All @@ -213,7 +213,7 @@ func Test_toKeyValueString_bad_target(t *testing.T) {
}
delimiter := ottl.NewTestingOptional[string]("=")
pairDelimiter := ottl.NewTestingOptional[string]("!")
exprFunc, err := toKeyValueString[any](target, delimiter, pairDelimiter)
exprFunc, err := toKeyValueString[any](target, delimiter, pairDelimiter, ottl.NewTestingOptional[bool](false))
assert.NoError(t, err)
_, err = exprFunc(context.Background(), nil)
assert.Error(t, err)
Expand All @@ -227,7 +227,7 @@ func Test_toKeyValueString_empty_target(t *testing.T) {
}
delimiter := ottl.NewTestingOptional[string]("=")
pairDelimiter := ottl.NewTestingOptional[string]("!")
exprFunc, err := toKeyValueString[any](target, delimiter, pairDelimiter)
exprFunc, err := toKeyValueString[any](target, delimiter, pairDelimiter, ottl.NewTestingOptional[bool](false))
assert.NoError(t, err)
_, err = exprFunc(context.Background(), nil)
assert.Error(t, err)
Expand All @@ -241,9 +241,9 @@ func Test_toKeyValueString_empty_delimiters(t *testing.T) {
}
delimiter := ottl.NewTestingOptional[string]("")

_, err := toKeyValueString[any](target, delimiter, ottl.Optional[string]{})
_, err := toKeyValueString[any](target, delimiter, ottl.Optional[string]{}, ottl.NewTestingOptional[bool](false))
assert.ErrorContains(t, err, "delimiter cannot be set to an empty string")

_, err = toKeyValueString[any](target, ottl.Optional[string]{}, delimiter)
_, err = toKeyValueString[any](target, ottl.Optional[string]{}, delimiter, ottl.NewTestingOptional[bool](false))
assert.ErrorContains(t, err, "pair delimiter cannot be set to an empty string")
}

0 comments on commit 21b464c

Please sign in to comment.