package value
import (
"encoding"
"errors"
"fmt"
"reflect"
"time"
"github.com/grafana/agent/pkg/river/internal/reflectutil"
)
type Unmarshaler interface {
UnmarshalRiver(f func(v interface{}) error) error
}
func Decode(val Value, target interface{}) error {
rt := reflect.ValueOf(target)
if rt.Kind() != reflect.Pointer {
panic("river/value: Decode called with non-pointer value")
}
var d decoder
return d.decode(val, rt)
}
func DecodeCopy(val Value, target interface{}) error {
rt := reflect.ValueOf(target)
if rt.Kind() != reflect.Pointer {
panic("river/value: Decode called with non-pointer value")
}
d := decoder{makeCopy: true}
return d.decode(val, rt)
}
type decoder struct {
makeCopy bool
}
func (d *decoder) decode(val Value, into reflect.Value) error {
rawValue := val.rv
if rawValue.CanAddr() {
rawValue = rawValue.Addr()
}
for into.Kind() == reflect.Pointer {
switch {
case into.CanSet() && val.Type() == TypeNull:
into.Set(reflect.Zero(into.Type()))
return nil
case into.CanSet() && d.canDirectlyAssign(rawValue.Type(), into.Type()):
into.Set(rawValue)
return nil
case into.CanSet() && d.canDirectlyAssign(val.rv.Type(), into.Type()):
into.Set(val.rv)
return nil
}
if into.IsNil() {
into.Set(reflect.New(into.Type().Elem()))
}
into = into.Elem()
}
switch {
case into.CanSet() && val.Type() == TypeNull:
into.Set(reflect.Zero(into.Type()))
return nil
case into.CanSet() && d.canDirectlyAssign(val.rv.Type(), into.Type()):
into.Set(val.rv)
return nil
}
if into.Type() == goAny {
return d.decodeAny(val, into)
} else if ok, err := d.decodeFromInterface(val, into); ok {
return err
}
targetType := RiverType(into.Type())
convVal := val
switch {
case val.rv.Type() == goByteSlice && into.Type() == goString:
into.Set(val.rv.Convert(goString))
return nil
case val.rv.Type() == goString && into.Type() == goByteSlice:
into.Set(val.rv.Convert(goByteSlice))
return nil
case convVal.Type() != targetType:
converted, err := tryCapsuleConvert(convVal, into, targetType)
if err != nil {
return err
} else if converted {
return nil
}
convVal, err = convertValue(convVal, targetType)
if err != nil {
return err
}
}
switch convVal.Type() {
case TypeNumber:
into.Set(convertGoNumber(convVal.Number(), into.Type()))
return nil
case TypeString:
into.Set(reflect.ValueOf(convVal.Text()))
return nil
case TypeBool:
into.Set(reflect.ValueOf(convVal.Bool()))
return nil
case TypeArray:
return d.decodeArray(convVal, into)
case TypeObject:
return d.decodeObject(convVal, into)
case TypeFunction:
if convVal.rv.Type() == into.Type() {
into.Set(convVal.rv)
return nil
}
return Error{
Value: val,
Inner: fmt.Errorf("expected function(%s), got function(%s)", into.Type(), convVal.rv.Type()),
}
case TypeCapsule:
if convVal.rv.Type() == into.Type() {
into.Set(convVal.rv)
return nil
}
converted, err := tryCapsuleConvert(convVal, into, targetType)
if err != nil {
return err
} else if converted {
return nil
}
return Error{
Value: val,
Inner: fmt.Errorf("expected capsule(%q), got %s", into.Type(), convVal.Describe()),
}
default:
panic("river/value: unexpected kind " + convVal.Type().String())
}
}
func (d *decoder) canDirectlyAssign(from reflect.Type, into reflect.Type) bool {
if d.makeCopy {
return false
}
if from != into {
return false
}
return !containsAny(into)
}
func containsAny(into reflect.Type) bool {
if into == goAny {
return true
}
switch into.Kind() {
case reflect.Array, reflect.Pointer, reflect.Slice:
return containsAny(into.Elem())
case reflect.Map:
if into.Key() == goString {
return containsAny(into.Elem())
}
return false
case reflect.Struct:
for i := 0; i < into.NumField(); i++ {
if containsAny(into.Field(i).Type) {
return true
}
}
return false
default:
return false
}
}
func (d *decoder) decodeFromInterface(val Value, into reflect.Value) (ok bool, err error) {
if into.CanAddr() {
into = into.Addr()
}
switch {
case into.Type() == goDurationPtr:
var s string
err := d.decode(val, reflect.ValueOf(&s))
if err != nil {
return true, err
}
dur, err := time.ParseDuration(s)
if err != nil {
return true, Error{Value: val, Inner: err}
}
*into.Interface().(*time.Duration) = dur
return true, nil
case into.Type().Implements(goRiverDecoder):
err := into.Interface().(Unmarshaler).UnmarshalRiver(func(v interface{}) error {
return d.decode(val, reflect.ValueOf(v))
})
if err != nil {
return true, Error{Value: val, Inner: err}
}
return true, nil
case into.Type().Implements(goTextUnmarshaler):
var s string
err := d.decode(val, reflect.ValueOf(&s))
if err != nil {
return true, err
}
err = into.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s))
if err != nil {
return true, Error{Value: val, Inner: err}
}
return true, nil
}
return false, nil
}
func tryCapsuleConvert(from Value, into reflect.Value, intoType Type) (ok bool, err error) {
if from.Type() == TypeCapsule {
cc, ok := from.Interface().(ConvertibleIntoCapsule)
if ok {
err := cc.ConvertInto(into.Addr().Interface())
if err == nil {
return true, nil
} else if err != nil && !errors.Is(err, ErrNoConversion) {
return false, Error{Value: from, Inner: err}
}
}
}
if intoType == TypeCapsule {
cc, ok := into.Addr().Interface().(ConvertibleFromCapsule)
if ok {
err := cc.ConvertFrom(from.Interface())
if err == nil {
return true, nil
} else if err != nil && !errors.Is(err, ErrNoConversion) {
return false, Error{Value: from, Inner: err}
}
}
}
if from.Type() == TypeCapsule && intoType == TypeCapsule && into.Kind() == reflect.Interface {
if from.Reflect().CanAddr() && from.Reflect().Addr().CanConvert(into.Type()) {
val := from.Reflect().Addr().Convert(into.Type())
into.Set(val)
return true, nil
} else if from.Reflect().CanConvert(into.Type()) {
val := from.Reflect().Convert(into.Type())
into.Set(val)
return true, nil
}
}
return false, nil
}
func (d *decoder) decodeAny(val Value, into reflect.Value) error {
var ptr reflect.Value
switch val.Type() {
case TypeNull:
into.Set(reflect.Zero(into.Type()))
return nil
case TypeNumber:
var v = val.Number().Float()
ptr = reflect.ValueOf(&v)
case TypeArray:
var v []interface{}
ptr = reflect.ValueOf(&v)
case TypeObject:
var v map[string]interface{}
ptr = reflect.ValueOf(&v)
case TypeBool:
var v bool
ptr = reflect.ValueOf(&v)
case TypeString:
var v string
ptr = reflect.ValueOf(&v)
case TypeFunction, TypeCapsule:
if val.rv.CanAddr() {
into.Set(val.rv.Addr())
return nil
}
into.Set(val.rv)
return nil
default:
panic("river/value: unreachable")
}
if err := d.decode(val, ptr); err != nil {
return err
}
into.Set(ptr.Elem())
return nil
}
func (d *decoder) decodeArray(val Value, rt reflect.Value) error {
switch rt.Kind() {
case reflect.Slice:
res := reflect.MakeSlice(rt.Type(), val.Len(), val.Len())
for i := 0; i < val.Len(); i++ {
if err := d.decode(val.Index(i), res.Index(i)); err != nil {
return ElementError{Value: val, Index: i, Inner: err}
}
}
rt.Set(res)
case reflect.Array:
res := reflect.New(rt.Type()).Elem()
if val.Len() != res.Len() {
return Error{
Value: val,
Inner: fmt.Errorf("array must have exactly %d elements, got %d", res.Len(), val.Len()),
}
}
for i := 0; i < val.Len(); i++ {
if err := d.decode(val.Index(i), res.Index(i)); err != nil {
return ElementError{Value: val, Index: i, Inner: err}
}
}
rt.Set(res)
default:
panic(fmt.Sprintf("river/value: unexpected array type %s", val.rv.Kind()))
}
return nil
}
func (d *decoder) decodeObject(val Value, rt reflect.Value) error {
switch rt.Kind() {
case reflect.Struct:
targetTags := getCachedTags(rt.Type())
return d.decodeObjectToStruct(val, rt, targetTags, false)
case reflect.Slice, reflect.Array:
keys := val.Keys()
var res reflect.Value
if rt.Kind() == reflect.Slice {
res = reflect.MakeSlice(rt.Type(), len(keys), len(keys))
} else {
res = reflect.New(rt.Type()).Elem()
if res.Len() != len(keys) {
return Error{
Value: val,
Inner: fmt.Errorf("object must have exactly %d keys, got %d", res.Len(), len(keys)),
}
}
}
fields := getCachedTags(rt.Type().Elem())
labelField, _ := fields.LabelField()
for i, key := range keys {
elem := res.Index(i)
reflectutil.GetOrAlloc(elem, labelField).Set(reflect.ValueOf(key))
value, _ := val.Key(key)
if err := d.decodeObjectToStruct(value, elem, fields, true); err != nil {
return FieldError{Value: val, Field: key, Inner: err}
}
}
rt.Set(res)
case reflect.Map:
if rt.Type().Key() != goString {
return TypeError{Value: val, Expected: RiverType(rt.Type())}
}
res := reflect.MakeMapWithSize(rt.Type(), val.Len())
into := reflect.New(rt.Type().Elem()).Elem()
intoZero := reflect.Zero(into.Type())
for i, key := range val.Keys() {
value, _ := val.Key(key)
if i > 0 {
into.Set(intoZero)
}
if err := d.decode(value, into); err != nil {
return FieldError{Value: val, Field: key, Inner: err}
}
res.SetMapIndex(reflect.ValueOf(key), into)
}
rt.Set(res)
default:
panic(fmt.Sprintf("river/value: unexpected target type %s", rt.Kind()))
}
return nil
}
func (d *decoder) decodeObjectToStruct(val Value, rt reflect.Value, fields *objectFields, decodedLabel bool) error {
for _, key := range val.Keys() {
value, _ := val.Key(key)
if lf, ok := fields.LabelField(); ok && !decodedLabel {
if value.Type() != TypeObject {
return FieldError{
Value: val,
Field: key,
Inner: TypeError{Value: value, Expected: TypeObject},
}
}
reflectutil.GetOrAlloc(rt, lf).Set(reflect.ValueOf(key))
if err := d.decodeObjectToStruct(value, rt, fields, true); err != nil {
return err
}
continue
}
switch fields.Has(key) {
case objectKeyTypeInvalid:
return MissingKeyError{Value: value, Missing: key}
case objectKeyTypeNestedField:
next, _ := fields.NestedField(key)
if err := d.decodeObjectToStruct(value, rt, next, decodedLabel); err != nil {
return err
}
case objectKeyTypeField:
targetField, _ := fields.Field(key)
targetValue := reflectutil.GetOrAlloc(rt, targetField)
if err := d.decode(value, targetValue); err != nil {
return FieldError{Value: val, Field: key, Inner: err}
}
}
}
return nil
}