Skip to content

Commit 862992b

Browse files
[chore][pkg/ottl] Add OTTL context utilities for handling slices accessors (#38928)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Currently, OTTL contexts setters can received different data type when using slices, which might lead to unexpected outcomes, for example, slice setters functions can receive the following value types: - `[]any` when set from literals: `set(string_table, ["foo", "bar"])` - integer values within the `[]any` are parsed as `int64` - `[]T` when copied from another path: `set(slice_path, slice_path)` - `pcommon.Slice` when set from an map's key value: `set(slice_path, resource.attributes["slice"])` Those different possibilities must be properly handled on the setter assessors, and are not obvious since the value is any-wrapped. Failing to handle all those possible types results in the path not being set or errors. This PR adds a set of functions that helps setting and getting slices values, making it easier handling them until OTTL slice parsing is not standardized. Those functions will be especially useful for the new profile context (#37574), which needs to provide access to different pdata typed slices. We've recently done something similar to maps (#38434). Usage examples: ```go // tCtx.GetProfile().StringTable() -> pcommon.StringSlice func accessStringTableKey[K ProfileContext](ctx context.Context, keys []ottl.Key[K]) ottl.StandardGetSetter[K] { return ottl.StandardGetSetter[K]{ Getter: func(_ context.Context, tCtx K) (any, error) { return ctxutil.GetCommonTypedSliceValue[K, string](ctx, tCtx, tCtx.GetProfile().StringTable(), keys) }, Setter: func(_ context.Context, tCtx K, val any) error { return ctxutil.SetCommonTypedSliceValue[K, string](ctx, tCtx, tCtx.GetProfile().StringTable(), keys, val) }, } } func accessStringTable[K ProfileContext]() ottl.StandardGetSetter[K] { return ottl.StandardGetSetter[K]{ Getter: func(_ context.Context, tCtx K) (any, error) { return tCtx.GetProfile().StringTable().AsRaw(), nil }, Setter: func(_ context.Context, tCtx K, val any) error { return ctxutil.SetCommonTypedSliceValues[string](tCtx.GetProfile().StringTable(), val) }, } } // *Int* function versions also handle type conversions and ensure returned values are compatible with OTTL defaults (int64). // tCtx.GetProfile().LocationIndices() -> pcommon.Int32Slice func accessLocationIndicesKey[K ProfileContext](ctx context.Context, keys []ottl.Key[K]) ottl.StandardGetSetter[K] { return ottl.StandardGetSetter[K]{ Getter: func(_ context.Context, tCtx K) (any, error) { return ctxutil.GetCommonIntSliceValue[K, int32](ctx, tCtx, tCtx.GetProfile().LocationIndices(), keys) }, Setter: func(_ context.Context, tCtx K, val any) error { return ctxutil.SetCommonIntSliceValue[K, int32](ctx, tCtx, tCtx.GetProfile().LocationIndices(), keys, val) }, } } func accessLocationIndices[K ProfileContext]() ottl.StandardGetSetter[K] { return ottl.StandardGetSetter[K]{ Getter: func(_ context.Context, tCtx K) (any, error) { return ctxutil.GetCommonIntSliceValues[int32](tCtx.GetProfile().LocationIndices()), nil }, Setter: func(_ context.Context, tCtx K, val any) error { return ctxutil.SetCommonIntSliceValues[int32](tCtx.GetProfile().LocationIndices(), val) }, } } ``` <!--Describe what testing was performed and which tests were added.--> #### Testing - Unit tests <!--Please delete paragraphs that you did not use before submitting.--> --------- Co-authored-by: Tim Rühsen <[email protected]>
1 parent 1295171 commit 862992b

File tree

2 files changed

+783
-19
lines changed

2 files changed

+783
-19
lines changed

pkg/ottl/contexts/internal/ctxutil/slice.go

Lines changed: 213 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,255 @@ package ctxutil // import "github.com/open-telemetry/opentelemetry-collector-con
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910

1011
"go.opentelemetry.io/collector/pdata/pcommon"
12+
"golang.org/x/exp/constraints"
1113

1214
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
1315
)
1416

15-
func GetSliceValue[K any](ctx context.Context, tCtx K, s pcommon.Slice, keys []ottl.Key[K]) (any, error) {
16-
if len(keys) == 0 {
17-
return nil, fmt.Errorf("cannot get slice value without key")
18-
}
17+
const (
18+
typeNotIndexableError = "type %T does not support indexing"
19+
)
1920

21+
var (
22+
errMissingSetKey = errors.New("cannot set slice value without key")
23+
errMissingGetKey = errors.New("cannot get slice value without key")
24+
)
25+
26+
func getSliceIndexFromKeys[K any](ctx context.Context, tCtx K, sliceLen int, keys []ottl.Key[K]) (int, error) {
2027
i, err := keys[0].Int(ctx, tCtx)
2128
if err != nil {
22-
return nil, err
29+
return 0, err
2330
}
2431
if i == nil {
2532
resInt, err := FetchValueFromExpression[K, int64](ctx, tCtx, keys[0])
2633
if err != nil {
27-
return nil, fmt.Errorf("unable to resolve an integer index in slice: %w", err)
34+
return 0, fmt.Errorf("unable to resolve an integer index in slice: %w", err)
2835
}
2936
i = resInt
3037
}
3138

3239
idx := int(*i)
3340

34-
if idx < 0 || idx >= s.Len() {
35-
return nil, fmt.Errorf("index %d out of bounds", idx)
41+
if idx < 0 || idx >= sliceLen {
42+
return 0, fmt.Errorf("index %d out of bounds", idx)
43+
}
44+
45+
return idx, nil
46+
}
47+
48+
func GetSliceValue[K any](ctx context.Context, tCtx K, s pcommon.Slice, keys []ottl.Key[K]) (any, error) {
49+
if len(keys) == 0 {
50+
return 0, errMissingGetKey
51+
}
52+
53+
idx, err := getSliceIndexFromKeys(ctx, tCtx, s.Len(), keys)
54+
if err != nil {
55+
return nil, err
3656
}
3757

3858
return getIndexableValue[K](ctx, tCtx, s.At(idx), keys[1:])
3959
}
4060

4161
func SetSliceValue[K any](ctx context.Context, tCtx K, s pcommon.Slice, keys []ottl.Key[K], val any) error {
4262
if len(keys) == 0 {
43-
return fmt.Errorf("cannot set slice value without key")
63+
return errMissingSetKey
4464
}
4565

46-
i, err := keys[0].Int(ctx, tCtx)
66+
idx, err := getSliceIndexFromKeys(ctx, tCtx, s.Len(), keys)
4767
if err != nil {
4868
return err
4969
}
50-
if i == nil {
51-
resInt, err := FetchValueFromExpression[K, int64](ctx, tCtx, keys[0])
52-
if err != nil {
53-
return fmt.Errorf("unable to resolve an integer index in slice: %w", err)
70+
71+
return SetIndexableValue[K](ctx, tCtx, s.At(idx), val, keys[1:])
72+
}
73+
74+
// CommonTypedSlice is an interface for typed pdata slices, such as pcommon.StringSlice,
75+
// pcommon.Int64Slice, pcommon.Int32Slice, etc.
76+
type CommonTypedSlice[T any] interface {
77+
At(int) T
78+
Len() int
79+
FromRaw(val []T)
80+
SetAt(int, T)
81+
AsRaw() []T
82+
}
83+
84+
// GetCommonTypedSliceValue is like GetSliceValue, but for retrieving a value from a pdata
85+
// typed slice. [V] is the type of the slice elements. If no keys are provided, it returns
86+
// an error. This function does not support slice elements indexing.
87+
func GetCommonTypedSliceValue[K, V any](ctx context.Context, tCtx K, s CommonTypedSlice[V], keys []ottl.Key[K]) (V, error) {
88+
if len(keys) == 0 {
89+
return *new(V), errMissingGetKey
90+
}
91+
if len(keys) > 1 {
92+
return *new(V), fmt.Errorf(typeNotIndexableError, s)
93+
}
94+
95+
idx, err := getSliceIndexFromKeys(ctx, tCtx, s.Len(), keys)
96+
if err != nil {
97+
return *new(V), err
98+
}
99+
100+
return s.At(idx), nil
101+
}
102+
103+
// SetCommonTypedSliceValue sets the value of a pdata typed slice element. [V] is the type
104+
// of the slice elements. The any-wrapped value type must be [V], otherwise an error is
105+
// returned. This function does not support slice elements indexing.
106+
func SetCommonTypedSliceValue[K, V any](ctx context.Context, tCtx K, s CommonTypedSlice[V], keys []ottl.Key[K], val any) error {
107+
if len(keys) == 0 {
108+
return errMissingSetKey
109+
} else if len(keys) > 1 {
110+
return fmt.Errorf(typeNotIndexableError, s)
111+
}
112+
113+
idx, err := getSliceIndexFromKeys(ctx, tCtx, s.Len(), keys)
114+
if err != nil {
115+
return err
116+
}
117+
118+
typeVal, ok := val.(V)
119+
if !ok {
120+
return fmt.Errorf("invalid value type provided for a slice of %T: %T", *new(V), val)
121+
}
122+
123+
s.SetAt(idx, typeVal)
124+
return nil
125+
}
126+
127+
// SetCommonTypedSliceValues sets the value of a pdata typed slice. It does handle all
128+
// different input types OTTL generate, such as []V, []any, or a pcommon.Slice.
129+
// If the value is a slice of [any] or pcommon.Slice, and it has an element that the type
130+
// is not [V], an error is returned.
131+
func SetCommonTypedSliceValues[V any](s CommonTypedSlice[V], val any) error {
132+
switch typeVal := val.(type) {
133+
case CommonTypedSlice[V]:
134+
s.FromRaw(typeVal.AsRaw())
135+
case []V:
136+
s.FromRaw(typeVal)
137+
case []any:
138+
raw := make([]V, len(typeVal))
139+
for i, v := range typeVal {
140+
sv, ok := v.(V)
141+
if !ok {
142+
return fmt.Errorf("invalid value type provided for a slice of %T: %T", *new(V), v)
143+
}
144+
raw[i] = sv
54145
}
55-
i = resInt
146+
s.FromRaw(raw)
147+
case pcommon.Slice:
148+
raw := make([]V, typeVal.Len())
149+
for i := range typeVal.Len() {
150+
v, ok := typeVal.At(i).AsRaw().(V)
151+
if !ok {
152+
return fmt.Errorf("invalid value type provided for a slice of %T: %T", raw, typeVal.At(i).AsRaw())
153+
}
154+
raw[i] = v
155+
}
156+
s.FromRaw(raw)
157+
default:
158+
return fmt.Errorf("invalid type provided for setting a slice of %T: %T", val, *new(V))
56159
}
57160

58-
idx := int(*i)
161+
return nil
162+
}
59163

60-
if idx < 0 || idx >= s.Len() {
61-
return fmt.Errorf("index %d out of bounds", idx)
164+
// GetCommonIntSliceValues converts a pdata typed slice of [constraints.Integer] into
165+
// []int64, which is the standard OTTL type for integer slices.
166+
func GetCommonIntSliceValues[V constraints.Integer](val CommonTypedSlice[V]) []int64 {
167+
output := make([]int64, val.Len())
168+
for i := range val.Len() {
169+
output[i] = int64(val.At(i))
62170
}
171+
return output
172+
}
63173

64-
return SetIndexableValue[K](ctx, tCtx, s.At(idx), val, keys[1:])
174+
// GetCommonIntSliceValue is like GetCommonTypedSliceValue, but for integer pdata typed
175+
// slices.
176+
func GetCommonIntSliceValue[K any, V constraints.Integer](ctx context.Context, tCtx K, s CommonTypedSlice[V], keys []ottl.Key[K]) (int64, error) {
177+
value, err := GetCommonTypedSliceValue[K, V](ctx, tCtx, s, keys)
178+
if err != nil {
179+
return 0, err
180+
}
181+
return int64(value), nil
182+
}
183+
184+
// SetCommonIntSliceValue is like SetCommonTypedSliceValue, but for integer pdata typed
185+
// slice element values. [V] is the type of the slice elements.
186+
// The any-wrapped value type must be and integer convertible to [V], otherwise an error
187+
// is returned. This function does not support slice elements indexing.
188+
func SetCommonIntSliceValue[K any, V constraints.Integer](ctx context.Context, tCtx K, s CommonTypedSlice[V], keys []ottl.Key[K], val any) error {
189+
var intVal V
190+
switch typeVal := val.(type) {
191+
case int:
192+
intVal = V(typeVal)
193+
case int8:
194+
intVal = V(typeVal)
195+
case int16:
196+
intVal = V(typeVal)
197+
case int32:
198+
intVal = V(typeVal)
199+
case int64:
200+
intVal = V(typeVal)
201+
case uint:
202+
intVal = V(typeVal)
203+
case uint8:
204+
intVal = V(typeVal)
205+
case uint16:
206+
intVal = V(typeVal)
207+
case uint32:
208+
intVal = V(typeVal)
209+
case uint64:
210+
intVal = V(typeVal)
211+
default:
212+
return fmt.Errorf("invalid type provided for setting a slice of %T: %T", *new(V), val)
213+
}
214+
215+
return SetCommonTypedSliceValue[K, V](ctx, tCtx, s, keys, intVal)
216+
}
217+
218+
// SetCommonIntSliceValues is like SetCommonTypedSliceValues, but for integer pdata typed
219+
// slices. [V] is the type of the slice elements. The value must be []any, []int64, []T,
220+
// or a pcommon.Slice which elements are type inferable to int64, otherwise an error is
221+
// returned.
222+
func SetCommonIntSliceValues[V constraints.Integer](s CommonTypedSlice[V], val any) error {
223+
switch typeVal := val.(type) {
224+
case CommonTypedSlice[V]:
225+
s.FromRaw(typeVal.AsRaw())
226+
case []int64:
227+
raw := make([]V, len(typeVal))
228+
for i, v := range typeVal {
229+
raw[i] = V(v)
230+
}
231+
s.FromRaw(raw)
232+
case []V:
233+
s.FromRaw(typeVal)
234+
case []any:
235+
raw := make([]V, len(typeVal))
236+
for i, v := range typeVal {
237+
iv, ok := v.(int64)
238+
if !ok {
239+
return fmt.Errorf("invalid value type provided for a slice of %T: %T, expected int64", *new(V), v)
240+
}
241+
raw[i] = V(iv)
242+
}
243+
s.FromRaw(raw)
244+
case pcommon.Slice:
245+
raw := make([]V, typeVal.Len())
246+
for i := range typeVal.Len() {
247+
v, ok := typeVal.At(i).AsRaw().(int64)
248+
if !ok {
249+
return fmt.Errorf("invalid value type provided for slice of %T: %T, expected int64", *new(V), typeVal.At(i).AsRaw())
250+
}
251+
raw[i] = V(v)
252+
}
253+
s.FromRaw(raw)
254+
default:
255+
return fmt.Errorf("cannot set a slice of %T from a value type: %T", val, *new(V))
256+
}
257+
258+
return nil
65259
}

0 commit comments

Comments
 (0)