pkg/ottl/expression.go (739 lines of code) (raw):

// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" import ( "bytes" "context" "encoding/binary" "encoding/hex" "fmt" "reflect" "strconv" "time" "github.com/goccy/go-json" "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/internal/ottlcommon" ) type ExprFunc[K any] func(ctx context.Context, tCtx K) (any, error) type Expr[K any] struct { exprFunc ExprFunc[K] } func (e Expr[K]) Eval(ctx context.Context, tCtx K) (any, error) { return e.exprFunc(ctx, tCtx) } // Getter resolves a value at runtime without performing any type checking on the value that is returned. type Getter[K any] interface { // Get retrieves a value of type 'Any' and returns an error if there are any issues during retrieval. Get(ctx context.Context, tCtx K) (any, error) } // Setter allows setting an untyped value on a predefined field within some data at runtime. type Setter[K any] interface { // Set sets a value of type 'Any' and returns an error if there are any issues during the setting process. Set(ctx context.Context, tCtx K, val any) error } // GetSetter is an interface that combines the Getter and Setter interfaces. // It should be used to represent the ability to both get and set a value. type GetSetter[K any] interface { Getter[K] Setter[K] } type StandardGetSetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) Setter func(ctx context.Context, tCtx K, val any) error } func (path StandardGetSetter[K]) Get(ctx context.Context, tCtx K) (any, error) { return path.Getter(ctx, tCtx) } func (path StandardGetSetter[K]) Set(ctx context.Context, tCtx K, val any) error { return path.Setter(ctx, tCtx, val) } type literal[K any] struct { value any } func (l literal[K]) Get(context.Context, K) (any, error) { return l.value, nil } type exprGetter[K any] struct { expr Expr[K] keys []key } func (g exprGetter[K]) Get(ctx context.Context, tCtx K) (any, error) { result, err := g.expr.Eval(ctx, tCtx) if err != nil { return nil, err } if g.keys == nil { return result, nil } for _, k := range g.keys { switch { case k.String != nil: switch r := result.(type) { case pcommon.Map: val, ok := r.Get(*k.String) if !ok { return nil, fmt.Errorf("key not found in map") } result = ottlcommon.GetValue(val) case map[string]any: val, ok := r[*k.String] if !ok { return nil, fmt.Errorf("key not found in map") } result = val default: return nil, fmt.Errorf("type, %T, does not support string indexing", result) } case k.Int != nil: switch r := result.(type) { case pcommon.Slice: if int(*k.Int) >= r.Len() || int(*k.Int) < 0 { return nil, fmt.Errorf("index %v out of bounds", *k.Int) } result = ottlcommon.GetValue(r.At(int(*k.Int))) case []any: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } case []string: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } case []bool: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } case []float64: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } case []int64: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } case []byte: result, err = getElementByIndex(r, k.Int) if err != nil { return nil, err } default: return nil, fmt.Errorf("type, %T, does not support int indexing", result) } default: return nil, fmt.Errorf("neither map nor slice index were set; this is an error in OTTL") } } return result, nil } func getElementByIndex[T any](r []T, idx *int64) (any, error) { if int(*idx) >= len(r) || int(*idx) < 0 { return nil, fmt.Errorf("index %v out of bounds", *idx) } return r[*idx], nil } type listGetter[K any] struct { slice []Getter[K] } func (l *listGetter[K]) Get(ctx context.Context, tCtx K) (any, error) { evaluated := make([]any, len(l.slice)) for i, v := range l.slice { val, err := v.Get(ctx, tCtx) if err != nil { return nil, err } evaluated[i] = val } return evaluated, nil } type mapGetter[K any] struct { mapValues map[string]Getter[K] } func (m *mapGetter[K]) Get(ctx context.Context, tCtx K) (any, error) { result := pcommon.NewMap() for k, v := range m.mapValues { val, err := v.Get(ctx, tCtx) if err != nil { return nil, err } switch typedVal := val.(type) { case pcommon.Map: target := result.PutEmpty(k).SetEmptyMap() typedVal.CopyTo(target) case []any: target := result.PutEmpty(k).SetEmptySlice() for _, el := range typedVal { switch typedEl := el.(type) { case pcommon.Map: m := target.AppendEmpty().SetEmptyMap() typedEl.CopyTo(m) default: err := target.AppendEmpty().FromRaw(el) if err != nil { return nil, err } } } default: err := result.PutEmpty(k).FromRaw(val) if err != nil { return nil, err } } } return result, nil } // TypeError represents that a value was not an expected type. type TypeError string func (t TypeError) Error() string { return string(t) } // StringGetter is a Getter that must return a string. type StringGetter[K any] interface { // Get retrieves a string value. Get(ctx context.Context, tCtx K) (string, error) } // StandardStringGetter is a basic implementation of StringGetter type StandardStringGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves a string value. // If the value is not a string a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardStringGetter[K]) Get(ctx context.Context, tCtx K) (string, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return "", fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return "", TypeError("expected string but got nil") } switch v := val.(type) { case string: return v, nil case pcommon.Value: if v.Type() == pcommon.ValueTypeStr { return v.Str(), nil } return "", TypeError(fmt.Sprintf("expected string but got %v", v.Type())) default: return "", TypeError(fmt.Sprintf("expected string but got %T", val)) } } // IntGetter is a Getter that must return an int64. type IntGetter[K any] interface { // Get retrieves an int64 value. Get(ctx context.Context, tCtx K) (int64, error) } // StandardIntGetter is a basic implementation of IntGetter type StandardIntGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves an int64 value. // If the value is not an int64 a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardIntGetter[K]) Get(ctx context.Context, tCtx K) (int64, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return 0, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return 0, TypeError("expected int64 but got nil") } switch v := val.(type) { case int64: return v, nil case pcommon.Value: if v.Type() == pcommon.ValueTypeInt { return v.Int(), nil } return 0, TypeError(fmt.Sprintf("expected int64 but got %v", v.Type())) default: return 0, TypeError(fmt.Sprintf("expected int64 but got %T", val)) } } // FloatGetter is a Getter that must return a float64. type FloatGetter[K any] interface { // Get retrieves a float64 value. Get(ctx context.Context, tCtx K) (float64, error) } // StandardFloatGetter is a basic implementation of FloatGetter type StandardFloatGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves a float64 value. // If the value is not a float64 a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardFloatGetter[K]) Get(ctx context.Context, tCtx K) (float64, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return 0, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return 0, TypeError("expected float64 but got nil") } switch v := val.(type) { case float64: return v, nil case pcommon.Value: if v.Type() == pcommon.ValueTypeDouble { return v.Double(), nil } return 0, TypeError(fmt.Sprintf("expected float64 but got %v", v.Type())) default: return 0, TypeError(fmt.Sprintf("expected float64 but got %T", val)) } } // BoolGetter is a Getter that must return a bool. type BoolGetter[K any] interface { // Get retrieves a bool value. Get(ctx context.Context, tCtx K) (bool, error) } // StandardBoolGetter is a basic implementation of BoolGetter type StandardBoolGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves a bool value. // If the value is not a bool a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardBoolGetter[K]) Get(ctx context.Context, tCtx K) (bool, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return false, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return false, TypeError("expected bool but got nil") } switch v := val.(type) { case bool: return v, nil case pcommon.Value: if v.Type() == pcommon.ValueTypeBool { return v.Bool(), nil } return false, TypeError(fmt.Sprintf("expected bool but got %v", v.Type())) default: return false, TypeError(fmt.Sprintf("expected bool but got %T", val)) } } // FunctionGetter uses a function factory to return an instantiated function as an Expr. type FunctionGetter[K any] interface { // Get returns a function as an Expr[K] built with the provided Arguments Get(args Arguments) (Expr[K], error) } // StandardFunctionGetter is a basic implementation of FunctionGetter. type StandardFunctionGetter[K any] struct { FCtx FunctionContext Fact Factory[K] } // Get takes an Arguments struct containing arguments the caller wants passed to the // function and instantiates the function with those arguments. // If there is a mismatch between the function's signature and the arguments the caller // wants to pass to the function, an error is returned. func (g StandardFunctionGetter[K]) Get(args Arguments) (Expr[K], error) { if g.Fact == nil { return Expr[K]{}, fmt.Errorf("undefined function") } fArgs := g.Fact.CreateDefaultArguments() if reflect.TypeOf(fArgs).Kind() != reflect.Pointer { return Expr[K]{}, fmt.Errorf("factory for %q must return a pointer to an Arguments value in its CreateDefaultArguments method", g.Fact.Name()) } if reflect.TypeOf(args).Kind() != reflect.Pointer { return Expr[K]{}, fmt.Errorf("%q must be pointer to an Arguments value", reflect.TypeOf(args).Kind()) } fArgsVal := reflect.ValueOf(fArgs).Elem() argsVal := reflect.ValueOf(args).Elem() if fArgsVal.NumField() != argsVal.NumField() { return Expr[K]{}, fmt.Errorf("incorrect number of arguments. Expected: %d Received: %d", fArgsVal.NumField(), argsVal.NumField()) } for i := 0; i < fArgsVal.NumField(); i++ { field := argsVal.Field(i) fArgsVal.Field(i).Set(field) } fn, err := g.Fact.CreateFunction(g.FCtx, fArgs) if err != nil { return Expr[K]{}, fmt.Errorf("couldn't create function: %w", err) } return Expr[K]{exprFunc: fn}, nil } // PMapGetter is a Getter that must return a pcommon.Map. type PMapGetter[K any] interface { // Get retrieves a pcommon.Map value. Get(ctx context.Context, tCtx K) (pcommon.Map, error) } // StandardPMapGetter is a basic implementation of PMapGetter type StandardPMapGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves a pcommon.Map value. // If the value is not a pcommon.Map a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardPMapGetter[K]) Get(ctx context.Context, tCtx K) (pcommon.Map, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return pcommon.Map{}, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return pcommon.Map{}, TypeError("expected pcommon.Map but got nil") } switch v := val.(type) { case pcommon.Map: return v, nil case pcommon.Value: if v.Type() == pcommon.ValueTypeMap { return v.Map(), nil } return pcommon.Map{}, TypeError(fmt.Sprintf("expected pcommon.Map but got %v", v.Type())) case map[string]any: m := pcommon.NewMap() err = m.FromRaw(v) if err != nil { return pcommon.Map{}, err } return m, nil default: return pcommon.Map{}, TypeError(fmt.Sprintf("expected pcommon.Map but got %T", val)) } } // StringLikeGetter is a Getter that returns a string by converting the underlying value to a string if necessary. type StringLikeGetter[K any] interface { // Get retrieves a string value. // Unlike `StringGetter`, the expectation is that the underlying value is converted to a string if possible. // If the value cannot be converted to a string, nil and an error are returned. // If the value is nil, nil is returned without an error. Get(ctx context.Context, tCtx K) (*string, error) } type StandardStringLikeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } func (g StandardStringLikeGetter[K]) Get(ctx context.Context, tCtx K) (*string, error) { 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 string switch v := val.(type) { case string: result = v case []byte: result = hex.EncodeToString(v) case pcommon.Map: resultBytes, err := json.Marshal(v.AsRaw()) if err != nil { return nil, err } result = string(resultBytes) case pcommon.Slice: resultBytes, err := json.Marshal(v.AsRaw()) if err != nil { return nil, err } result = string(resultBytes) case pcommon.Value: result = v.AsString() default: resultBytes, err := json.Marshal(v) if err != nil { return nil, TypeError(fmt.Sprintf("unsupported type: %T", v)) } result = string(resultBytes) } return &result, nil } // FloatLikeGetter is a Getter that returns a float64 by converting the underlying value to a float64 if necessary. type FloatLikeGetter[K any] interface { // Get retrieves a float64 value. // Unlike `FloatGetter`, the expectation is that the underlying value is converted to a float64 if possible. // If the value cannot be converted to a float64, nil and an error are returned. // If the value is nil, nil is returned without an error. Get(ctx context.Context, tCtx K) (*float64, error) } type StandardFloatLikeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } func (g StandardFloatLikeGetter[K]) Get(ctx context.Context, tCtx K) (*float64, error) { 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 float64 switch v := val.(type) { case float64: result = v case int64: result = float64(v) case string: result, err = strconv.ParseFloat(v, 64) if err != nil { return nil, err } case bool: if v { result = float64(1) } else { result = float64(0) } case pcommon.Value: switch v.Type() { case pcommon.ValueTypeDouble: result = v.Double() case pcommon.ValueTypeInt: result = float64(v.Int()) case pcommon.ValueTypeStr: result, err = strconv.ParseFloat(v.Str(), 64) if err != nil { return nil, err } case pcommon.ValueTypeBool: if v.Bool() { result = float64(1) } else { result = float64(0) } 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 } // IntLikeGetter is a Getter that returns an int by converting the underlying value to an int if necessary type IntLikeGetter[K any] interface { // Get retrieves an int value. // Unlike `IntGetter`, the expectation is that the underlying value is converted to an int if possible. // If the value cannot be converted to an int, nil and an error are returned. // If the value is nil, nil is returned without an error. Get(ctx context.Context, tCtx K) (*int64, error) } type StandardIntLikeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } func (g StandardIntLikeGetter[K]) Get(ctx context.Context, tCtx K) (*int64, error) { 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 int64 switch v := val.(type) { case int64: result = v case string: result, err = strconv.ParseInt(v, 10, 64) if err != nil { return nil, nil } case float64: result = int64(v) case bool: if v { result = int64(1) } else { result = int64(0) } case pcommon.Value: switch v.Type() { case pcommon.ValueTypeInt: result = v.Int() case pcommon.ValueTypeDouble: result = int64(v.Double()) case pcommon.ValueTypeStr: result, err = strconv.ParseInt(v.Str(), 10, 64) if err != nil { return nil, nil } case pcommon.ValueTypeBool: if v.Bool() { result = int64(1) } else { result = int64(0) } 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 } // 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) ([]byte, error) } type StandardByteSliceLikeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } func (g StandardByteSliceLikeGetter[K]) Get(ctx context.Context, tCtx K) ([]byte, error) { 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 []byte switch v := val.(type) { case []byte: result = v case string: result = []byte(v) case float64, int64, bool: result, err = valueToBytes(v) if err != nil { return nil, fmt.Errorf("error converting value %f of %T: %w", v, g, err) } case pcommon.Value: switch v.Type() { case pcommon.ValueTypeBytes: result = v.Bytes().AsRaw() case pcommon.ValueTypeInt: result, err = valueToBytes(v.Int()) if err != nil { return nil, fmt.Errorf("error converting value %d of int64: %w", v.Int(), err) } case pcommon.ValueTypeDouble: result, err = valueToBytes(v.Double()) if err != nil { return nil, fmt.Errorf("error converting value %f of float64: %w", v.Double(), err) } case pcommon.ValueTypeStr: result = []byte(v.Str()) case pcommon.ValueTypeBool: result, err = valueToBytes(v.Bool()) if err != nil { return nil, fmt.Errorf("error converting value %s of bool: %w", v.Str(), err) } 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 } // valueToBytes converts a value to a byte slice of length 8. func valueToBytes(n any) ([]byte, error) { // Create a buffer to hold the bytes buf := new(bytes.Buffer) // Write the value to the buffer using binary.Write err := binary.Write(buf, binary.BigEndian, n) if err != nil { return nil, err } return buf.Bytes(), nil } // 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. // Unlike `BoolGetter`, the expectation is that the underlying value is converted to a bool if possible. // If the value cannot be converted to a bool, nil and an error are returned. // If the value is nil, nil is returned without an error. Get(ctx context.Context, tCtx K) (*bool, error) } type StandardBoolLikeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } func (g StandardBoolLikeGetter[K]) Get(ctx context.Context, tCtx K) (*bool, error) { 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 bool switch v := val.(type) { case bool: result = v case int: result = v != 0 case int64: result = v != 0 case string: result, err = strconv.ParseBool(v) if err != nil { return nil, err } case float64: result = v != 0.0 case pcommon.Value: switch v.Type() { case pcommon.ValueTypeBool: result = v.Bool() case pcommon.ValueTypeInt: result = v.Int() != 0 case pcommon.ValueTypeStr: result, err = strconv.ParseBool(v.Str()) if err != nil { return nil, err } case pcommon.ValueTypeDouble: result = v.Double() != 0.0 default: return nil, TypeError(fmt.Sprintf("unsupported value type: %v", v.Type())) } default: return nil, TypeError(fmt.Sprintf("unsupported type: %T", val)) } return &result, nil } func (p *Parser[K]) newGetter(val value) (Getter[K], error) { if val.IsNil != nil && *val.IsNil { return &literal[K]{value: nil}, nil } if s := val.String; s != nil { return &literal[K]{value: *s}, nil } if b := val.Bool; b != nil { return &literal[K]{value: bool(*b)}, nil } if b := val.Bytes; b != nil { return &literal[K]{value: ([]byte)(*b)}, nil } if val.Enum != nil { enum, err := p.enumParser((*EnumSymbol)(val.Enum)) if err != nil { return nil, err } return &literal[K]{value: int64(*enum)}, nil } if eL := val.Literal; eL != nil { if f := eL.Float; f != nil { return &literal[K]{value: *f}, nil } if i := eL.Int; i != nil { return &literal[K]{value: *i}, nil } if eL.Path != nil { np, err := p.newPath(eL.Path) if err != nil { return nil, err } return p.parsePath(np) } if eL.Converter != nil { return p.newGetterFromConverter(*eL.Converter) } } if val.List != nil { lg := listGetter[K]{slice: make([]Getter[K], len(val.List.Values))} for i, v := range val.List.Values { getter, err := p.newGetter(v) if err != nil { return nil, err } lg.slice[i] = getter } return &lg, nil } if val.Map != nil { mg := mapGetter[K]{mapValues: map[string]Getter[K]{}} for _, kvp := range val.Map.Values { getter, err := p.newGetter(*kvp.Value) if err != nil { return nil, err } mg.mapValues[*kvp.Key] = getter } return &mg, nil } if val.MathExpression == nil { // In practice, can't happen since the DSL grammar guarantees one is set return nil, fmt.Errorf("no value field set. This is a bug in the OpenTelemetry Transformation Language") } return p.evaluateMathExpression(val.MathExpression) } func (p *Parser[K]) newGetterFromConverter(c converter) (Getter[K], error) { call, err := p.newFunctionCall(editor(c)) if err != nil { return nil, err } return &exprGetter[K]{ expr: call, keys: c.Keys, }, nil } // TimeGetter is a Getter that must return a time.Time. type TimeGetter[K any] interface { // Get retrieves a time.Time value. Get(ctx context.Context, tCtx K) (time.Time, error) } // StandardTimeGetter is a basic implementation of TimeGetter type StandardTimeGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves a time.Time value. // If the value is not a time.Time, a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardTimeGetter[K]) Get(ctx context.Context, tCtx K) (time.Time, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return time.Time{}, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return time.Time{}, TypeError("expected time but got nil") } switch v := val.(type) { case time.Time: return v, nil default: return time.Time{}, TypeError(fmt.Sprintf("expected time but got %T", val)) } } // DurationGetter is a Getter that must return a time.Duration. type DurationGetter[K any] interface { // Get retrieves a time.Duration value. Get(ctx context.Context, tCtx K) (time.Duration, error) } // StandardDurationGetter is a basic implementation of DurationGetter type StandardDurationGetter[K any] struct { Getter func(ctx context.Context, tCtx K) (any, error) } // Get retrieves an time.Duration value. // If the value is not an time.Duration a new TypeError is returned. // If there is an error getting the value it will be returned. func (g StandardDurationGetter[K]) Get(ctx context.Context, tCtx K) (time.Duration, error) { val, err := g.Getter(ctx, tCtx) if err != nil { return 0, fmt.Errorf("error getting value in %T: %w", g, err) } if val == nil { return 0, TypeError("expected duration but got nil") } switch v := val.(type) { case time.Duration: return v, nil default: return 0, TypeError(fmt.Sprintf("expected duration but got %T", val)) } }