308 lines
7.4 KiB
Go
308 lines
7.4 KiB
Go
package starlark
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"go.starlark.net/starlark"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
)
|
|
|
|
// FieldDict is a starlark.Value for the metric fields. It is heavily based on the
|
|
// starlark.Dict.
|
|
type FieldDict struct {
|
|
*Metric
|
|
}
|
|
|
|
func (d FieldDict) String() string {
|
|
buf := new(strings.Builder)
|
|
buf.WriteString("{")
|
|
sep := ""
|
|
for _, item := range d.Items() {
|
|
k, v := item[0], item[1]
|
|
buf.WriteString(sep)
|
|
buf.WriteString(k.String())
|
|
buf.WriteString(": ")
|
|
buf.WriteString(v.String())
|
|
sep = ", "
|
|
}
|
|
buf.WriteString("}")
|
|
return buf.String()
|
|
}
|
|
|
|
func (FieldDict) Type() string {
|
|
return "Fields"
|
|
}
|
|
|
|
func (d FieldDict) Freeze() {
|
|
// Disable linter check as the frozen variable is modified despite
|
|
// passing a value instead of a pointer, because `FieldDict` holds
|
|
// a pointer to the underlying metric containing the `frozen` field.
|
|
//revive:disable:modifies-value-receiver
|
|
d.frozen = true
|
|
}
|
|
|
|
func (d FieldDict) Truth() starlark.Bool {
|
|
return len(d.metric.FieldList()) != 0
|
|
}
|
|
|
|
func (FieldDict) Hash() (uint32, error) {
|
|
return 0, errors.New("not hashable")
|
|
}
|
|
|
|
// AttrNames implements the starlark.HasAttrs interface.
|
|
func (FieldDict) AttrNames() []string {
|
|
return builtinAttrNames(FieldDictMethods)
|
|
}
|
|
|
|
// Attr implements the starlark.HasAttrs interface.
|
|
func (d FieldDict) Attr(name string) (starlark.Value, error) {
|
|
return builtinAttr(d, name, FieldDictMethods)
|
|
}
|
|
|
|
var FieldDictMethods = map[string]builtinMethod{
|
|
"clear": dictClear,
|
|
"get": dictGet,
|
|
"items": dictItems,
|
|
"keys": dictKeys,
|
|
"pop": dictPop,
|
|
"popitem": dictPopitem,
|
|
"setdefault": dictSetdefault,
|
|
"update": dictUpdate,
|
|
"values": dictValues,
|
|
}
|
|
|
|
// Get implements the starlark.Mapping interface.
|
|
func (d FieldDict) Get(key starlark.Value) (v starlark.Value, found bool, err error) {
|
|
if k, ok := key.(starlark.String); ok {
|
|
gv, found := d.metric.GetField(k.GoString())
|
|
if !found {
|
|
return starlark.None, false, nil
|
|
}
|
|
|
|
v, err := asStarlarkValue(gv)
|
|
if err != nil {
|
|
return starlark.None, false, err
|
|
}
|
|
return v, true, nil
|
|
}
|
|
|
|
return starlark.None, false, errors.New("key must be of type 'str'")
|
|
}
|
|
|
|
// SetKey implements the starlark.HasSetKey interface to support map update
|
|
// using x[k]=v syntax, like a dictionary.
|
|
func (d FieldDict) SetKey(k, v starlark.Value) error {
|
|
if d.fieldIterCount > 0 {
|
|
return errors.New("cannot insert during iteration")
|
|
}
|
|
|
|
key, ok := k.(starlark.String)
|
|
if !ok {
|
|
return errors.New("field key must be of type 'str'")
|
|
}
|
|
|
|
gv, err := asGoValue(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.metric.AddField(key.GoString(), gv)
|
|
return nil
|
|
}
|
|
|
|
// Items implements the starlark.IterableMapping interface.
|
|
func (d FieldDict) Items() []starlark.Tuple {
|
|
items := make([]starlark.Tuple, 0, len(d.metric.FieldList()))
|
|
for _, field := range d.metric.FieldList() {
|
|
key := starlark.String(field.Key)
|
|
sv, err := asStarlarkValue(field.Value)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
pair := starlark.Tuple{key, sv}
|
|
items = append(items, pair)
|
|
}
|
|
return items
|
|
}
|
|
|
|
func (d FieldDict) Clear() error {
|
|
if d.fieldIterCount > 0 {
|
|
return errors.New("cannot delete during iteration")
|
|
}
|
|
|
|
keys := make([]string, 0, len(d.metric.FieldList()))
|
|
for _, field := range d.metric.FieldList() {
|
|
keys = append(keys, field.Key)
|
|
}
|
|
|
|
for _, key := range keys {
|
|
d.metric.RemoveField(key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d FieldDict) PopItem() (starlark.Value, error) {
|
|
if d.fieldIterCount > 0 {
|
|
return nil, errors.New("cannot delete during iteration")
|
|
}
|
|
|
|
if len(d.metric.FieldList()) == 0 {
|
|
return nil, errors.New("popitem(): field dictionary is empty")
|
|
}
|
|
|
|
field := d.metric.FieldList()[0]
|
|
k := field.Key
|
|
v := field.Value
|
|
|
|
d.metric.RemoveField(k)
|
|
|
|
sk := starlark.String(k)
|
|
sv, err := asStarlarkValue(v)
|
|
if err != nil {
|
|
return nil, errors.New("could not convert to starlark value")
|
|
}
|
|
|
|
return starlark.Tuple{sk, sv}, nil
|
|
}
|
|
|
|
func (d FieldDict) Delete(k starlark.Value) (v starlark.Value, found bool, err error) {
|
|
if d.fieldIterCount > 0 {
|
|
return nil, false, errors.New("cannot delete during iteration")
|
|
}
|
|
|
|
if key, ok := k.(starlark.String); ok {
|
|
value, ok := d.metric.GetField(key.GoString())
|
|
if ok {
|
|
d.metric.RemoveField(key.GoString())
|
|
sv, err := asStarlarkValue(value)
|
|
return sv, ok, err
|
|
}
|
|
return starlark.None, false, nil
|
|
}
|
|
|
|
return starlark.None, false, errors.New("key must be of type 'str'")
|
|
}
|
|
|
|
// Iterate implements the starlark.Iterator interface.
|
|
func (d FieldDict) Iterate() starlark.Iterator {
|
|
d.fieldIterCount++
|
|
return &FieldIterator{Metric: d.Metric, fields: d.metric.FieldList()}
|
|
}
|
|
|
|
type FieldIterator struct {
|
|
*Metric
|
|
fields []*telegraf.Field
|
|
}
|
|
|
|
// Next implements the starlark.Iterator interface.
|
|
func (i *FieldIterator) Next(p *starlark.Value) bool {
|
|
if len(i.fields) == 0 {
|
|
return false
|
|
}
|
|
|
|
field := i.fields[0]
|
|
i.fields = i.fields[1:]
|
|
*p = starlark.String(field.Key)
|
|
|
|
return true
|
|
}
|
|
|
|
// Done implements the starlark.Iterator interface.
|
|
func (i *FieldIterator) Done() {
|
|
i.fieldIterCount--
|
|
}
|
|
|
|
// AsStarlarkValue converts a field value to a starlark.Value.
|
|
func asStarlarkValue(value interface{}) (starlark.Value, error) {
|
|
v := reflect.ValueOf(value)
|
|
switch v.Kind() {
|
|
case reflect.Slice:
|
|
length := v.Len()
|
|
array := make([]starlark.Value, 0, length)
|
|
for i := 0; i < length; i++ {
|
|
sVal, err := asStarlarkValue(v.Index(i).Interface())
|
|
if err != nil {
|
|
return starlark.None, err
|
|
}
|
|
array = append(array, sVal)
|
|
}
|
|
return starlark.NewList(array), nil
|
|
case reflect.Map:
|
|
dict := starlark.NewDict(v.Len())
|
|
iter := v.MapRange()
|
|
for iter.Next() {
|
|
sKey, err := asStarlarkValue(iter.Key().Interface())
|
|
if err != nil {
|
|
return starlark.None, err
|
|
}
|
|
sValue, err := asStarlarkValue(iter.Value().Interface())
|
|
if err != nil {
|
|
return starlark.None, err
|
|
}
|
|
if err := dict.SetKey(sKey, sValue); err != nil {
|
|
return starlark.None, err
|
|
}
|
|
}
|
|
return dict, nil
|
|
case reflect.Float32, reflect.Float64:
|
|
return starlark.Float(v.Float()), nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return starlark.MakeInt64(v.Int()), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return starlark.MakeUint64(v.Uint()), nil
|
|
case reflect.String:
|
|
return starlark.String(v.String()), nil
|
|
case reflect.Bool:
|
|
return starlark.Bool(v.Bool()), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid type %T", value)
|
|
}
|
|
|
|
// AsGoValue converts a starlark.Value to a field value.
|
|
func asGoValue(value interface{}) (interface{}, error) {
|
|
switch v := value.(type) {
|
|
case starlark.Float:
|
|
return float64(v), nil
|
|
case starlark.Int:
|
|
n, ok := v.Int64()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot represent integer %v as int64", v)
|
|
}
|
|
return n, nil
|
|
case starlark.String:
|
|
return string(v), nil
|
|
case starlark.Bool:
|
|
return bool(v), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid starlark type %T", value)
|
|
}
|
|
|
|
// ToFields converts a starlark.Value to a map of values.
|
|
func toFields(value starlark.Value) (map[string]interface{}, error) {
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
items, err := items(value, "The type %T is unsupported as type of collection of fields")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[string]interface{}, len(items))
|
|
for _, item := range items {
|
|
key, err := toString(item[0], "The type %T is unsupported as type of key for fields")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value, err := asGoValue(item[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[key] = value
|
|
}
|
|
return result, nil
|
|
}
|