587 lines
15 KiB
Go
587 lines
15 KiB
Go
package tftypes
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
msgpack "github.com/vmihailenco/msgpack/v4"
|
|
)
|
|
|
|
// ValueConverter is an interface that provider-defined types can implement to
|
|
// control how Value.As will convert a Value into that type. The passed Value
|
|
// is the Value that Value.As is being called on. The intended usage is to call
|
|
// Value.As on the passed Value, converting it into a builtin type, and then
|
|
// converting or casting that builtin type to the provider-defined type.
|
|
type ValueConverter interface {
|
|
FromTerraform5Value(Value) error
|
|
}
|
|
|
|
// ValueCreator is an interface that provider-defined types can implement to
|
|
// control how NewValue will convert that type into a Value. The returned
|
|
// interface should return one of the builtin Value representations that should
|
|
// be used for that Value.
|
|
type ValueCreator interface {
|
|
ToTerraform5Value() (interface{}, error)
|
|
}
|
|
|
|
// Value is a piece of data from Terraform or being returned to Terraform. It
|
|
// has a Type associated with it, defining its shape and characteristics, and a
|
|
// Go representation of that Type containing the data itself. Values are a
|
|
// special type and are not represented as pure Go values beause they can
|
|
// contain UnknownValues, which cannot be losslessly represented in Go's type
|
|
// system.
|
|
//
|
|
// The recommended usage of a Value is to check that it is known, using
|
|
// Value.IsKnown, then to convert it to a Go type, using Value.As. The Go type
|
|
// can then be manipulated.
|
|
type Value struct {
|
|
typ Type
|
|
value interface{}
|
|
}
|
|
|
|
func (val Value) String() string {
|
|
typ := val.Type()
|
|
|
|
if typ == nil {
|
|
return "invalid typeless tftypes.Value<>"
|
|
}
|
|
|
|
// null and unknown values we use static strings for
|
|
if val.IsNull() {
|
|
return typ.String() + "<null>"
|
|
}
|
|
if !val.IsKnown() {
|
|
return typ.String() + "<unknown>"
|
|
}
|
|
|
|
// everything else is built up
|
|
var res strings.Builder
|
|
switch {
|
|
case typ.Is(String):
|
|
var s string
|
|
err := val.As(&s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res.WriteString(typ.String() + `<"` + s + `">`)
|
|
case typ.Is(Number):
|
|
n := big.NewFloat(0)
|
|
err := val.As(&n)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res.WriteString(typ.String() + `<"` + n.String() + `">`)
|
|
case typ.Is(Bool):
|
|
var b bool
|
|
err := val.As(&b)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res.WriteString(typ.String() + `<"` + strconv.FormatBool(b) + `">`)
|
|
case typ.Is(List{}), typ.Is(Set{}), typ.Is(Tuple{}):
|
|
var l []Value
|
|
err := val.As(&l)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res.WriteString(typ.String() + `<`)
|
|
for pos, el := range l {
|
|
if pos != 0 {
|
|
res.WriteString(", ")
|
|
}
|
|
res.WriteString(el.String())
|
|
}
|
|
res.WriteString(">")
|
|
case typ.Is(Map{}), typ.Is(Object{}):
|
|
m := map[string]Value{}
|
|
err := val.As(&m)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res.WriteString(typ.String() + `<`)
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for pos, key := range keys {
|
|
if pos != 0 {
|
|
res.WriteString(", ")
|
|
}
|
|
res.WriteString(`"` + key + `":`)
|
|
res.WriteString(m[key].String())
|
|
}
|
|
res.WriteString(">")
|
|
}
|
|
return res.String()
|
|
}
|
|
|
|
// ApplyTerraform5AttributePathStep applies an AttributePathStep to a Value,
|
|
// returning the Value found at that AttributePath within the Value. It
|
|
// fulfills that AttributePathStepper interface, allowing Values to be passed
|
|
// to WalkAttributePath. This allows retrieving a subset of a Value using an
|
|
// AttributePath. If the AttributePathStep can't be applied to the Value,
|
|
// either because it is the wrong type or because no Value exists at that
|
|
// AttributePathStep, an ErrInvalidStep error will be returned.
|
|
func (val Value) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
|
|
if !val.IsKnown() || val.IsNull() {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
switch s := step.(type) {
|
|
case AttributeName:
|
|
if !val.Type().Is(Object{}) {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
o := map[string]Value{}
|
|
err := val.As(&o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, ok := o[string(s)]
|
|
if !ok {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
return res, nil
|
|
case ElementKeyString:
|
|
if !val.Type().Is(Map{}) {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
m := map[string]Value{}
|
|
err := val.As(&m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, ok := m[string(s)]
|
|
if !ok {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
return res, nil
|
|
case ElementKeyInt:
|
|
if !val.Type().Is(List{}) && !val.Type().Is(Tuple{}) {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
if int64(s) < 0 {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
sl := []Value{}
|
|
err := val.As(&sl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if int64(len(sl)) <= int64(s) {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
return sl[int64(s)], nil
|
|
case ElementKeyValue:
|
|
if !val.Type().Is(Set{}) {
|
|
return nil, ErrInvalidStep
|
|
}
|
|
sl := []Value{}
|
|
err := val.As(&sl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, el := range sl {
|
|
diffs, err := el.Diff(Value(s))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(diffs) == 0 {
|
|
return el, nil
|
|
}
|
|
}
|
|
return nil, ErrInvalidStep
|
|
default:
|
|
return nil, fmt.Errorf("unexpected AttributePathStep type %T", step)
|
|
}
|
|
}
|
|
|
|
// Equal returns true if two Values should be considered equal. Values are
|
|
// considered equal if their types are considered equal and if they represent
|
|
// data that is considered equal.
|
|
func (val Value) Equal(o Value) bool {
|
|
if val.Type() == nil && o.Type() == nil && val.value == nil && o.value == nil {
|
|
return true
|
|
}
|
|
if val.Type() == nil {
|
|
return false
|
|
}
|
|
if o.Type() == nil {
|
|
return false
|
|
}
|
|
if !val.Type().Equal(o.Type()) {
|
|
return false
|
|
}
|
|
diff, err := val.Diff(o)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return len(diff) < 1
|
|
}
|
|
|
|
// Copy returns a defensively-copied clone of Value that shares no underlying
|
|
// data structures with the original Value and can be mutated without
|
|
// accidentally mutating the original.
|
|
func (val Value) Copy() Value {
|
|
newVal := val.value
|
|
switch v := val.value.(type) {
|
|
case []Value:
|
|
newVals := make([]Value, 0, len(v))
|
|
for _, value := range v {
|
|
newVals = append(newVals, value.Copy())
|
|
}
|
|
newVal = newVals
|
|
case map[string]Value:
|
|
newVals := make(map[string]Value, len(v))
|
|
for k, value := range v {
|
|
newVals[k] = value.Copy()
|
|
}
|
|
newVal = newVals
|
|
}
|
|
return NewValue(val.Type(), newVal)
|
|
}
|
|
|
|
// NewValue returns a Value constructed using the specified Type and stores the
|
|
// passed value in it.
|
|
//
|
|
// The passed value should be in one of the builtin Value representations or
|
|
// implement the ValueCreator interface.
|
|
//
|
|
// If the passed value is not a valid value for the passed type, NewValue will
|
|
// panic. Any value and type combination that does not return an error from
|
|
// ValidateValue is guaranteed to not panic. When calling NewValue with user
|
|
// input with a type not known at compile time, it is recommended to call
|
|
// ValidateValue before calling NewValue, to allow graceful handling of the
|
|
// error.
|
|
//
|
|
// The builtin Value representations are:
|
|
//
|
|
// * String: string, *string
|
|
//
|
|
// * Number: *big.Float, int64, *int64, int32, *int32, int16, *int16, int8,
|
|
// *int8, int, *int, uint64, *uint64, uint32, *uint32, uint16,
|
|
// *uint16, uint8, *uint8, uint, *uint, float64, *float64
|
|
//
|
|
// * Bool: bool, *bool
|
|
//
|
|
// * Map and Object: map[string]Value
|
|
//
|
|
// * Tuple, List, and Set: []Value
|
|
func NewValue(t Type, val interface{}) Value {
|
|
v, err := newValue(t, val)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// ValidateValue checks that the Go type passed as `val` can be used as a value
|
|
// for the Type passed as `t`. A nil error response indicates that the value is
|
|
// valid for the type.
|
|
func ValidateValue(t Type, val interface{}) error {
|
|
_, err := newValue(t, val)
|
|
return err
|
|
}
|
|
|
|
func newValue(t Type, val interface{}) (Value, error) {
|
|
if val == nil || val == UnknownValue {
|
|
return Value{
|
|
typ: t,
|
|
value: val,
|
|
}, nil
|
|
}
|
|
|
|
if creator, ok := val.(ValueCreator); ok {
|
|
var err error
|
|
val, err = creator.ToTerraform5Value()
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("error creating tftypes.Value: %w", err)
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case t.Is(String):
|
|
v, err := valueFromString(val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Number):
|
|
v, err := valueFromNumber(val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Bool):
|
|
v, err := valueFromBool(val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Map{}):
|
|
v, err := valueFromMap(t.(Map).ElementType, val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Object{}):
|
|
v, err := valueFromObject(t.(Object).AttributeTypes, t.(Object).OptionalAttributes, val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(List{}):
|
|
v, err := valueFromList(t.(List).ElementType, val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Set{}):
|
|
v, err := valueFromSet(t.(Set).ElementType, val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(Tuple{}):
|
|
v, err := valueFromTuple(t.(Tuple).ElementTypes, val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
case t.Is(DynamicPseudoType):
|
|
v, err := valueFromDynamicPseudoType(val)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
return v, nil
|
|
default:
|
|
return Value{}, fmt.Errorf("unknown type %s passed to tftypes.NewValue", t)
|
|
}
|
|
}
|
|
|
|
// As converts a Value into a Go value. `dst` must be set to a pointer to a
|
|
// value of a supported type for the Value's type or an implementation of the
|
|
// ValueConverter interface.
|
|
//
|
|
// For Strings, `dst` must be a pointer to a string or a pointer to a pointer
|
|
// to a string. If it's a pointer to a pointer to a string, if the Value is
|
|
// null, the pointer to the string will be set to nil. If it's a pointer to a
|
|
// string, if the Value is null, the string will be set to the empty value.
|
|
//
|
|
// For Numbers, `dst` must be a poitner to a big.Float or a pointer to a
|
|
// pointer to a big.Float. If it's a pointer to a pointer to a big.Float, if
|
|
// the Value is null, the pointer to the big.Float will be set to nil. If it's
|
|
// a pointer to a big.Float, if the Value is null, the big.Float will be set to
|
|
// 0.
|
|
//
|
|
// For Bools, `dst` must be a pointer to a bool or a pointer to a pointer to a
|
|
// bool. If it's a pointer to a pointer to a bool, if the Value is null, the
|
|
// pointer to the bool will be set to nil. If it's a pointer to a bool, if the
|
|
// Value is null, the bool will be set to false.
|
|
//
|
|
// For Maps and Objects, `dst` must be a pointer to a map[string]Value or a
|
|
// pointer to a pointer to a map[string]Value. If it's a pointer to a pointer
|
|
// to a map[string]Value, if the Value is null, the pointer to the
|
|
// map[string]Value will be set to nil. If it's a pointer to a
|
|
// map[string]Value, if the Value is null, the map[string]Value will be set to
|
|
// an empty map.
|
|
//
|
|
// For Lists, Sets, and Tuples, `dst` must be a pointer to a []Value or a
|
|
// pointer to a pointer to a []Value. If it's a pointer to a pointer to a
|
|
// []Value, if the Value is null, the poitner to []Value will be set to nil. If
|
|
// it's a pointer to a []Value, if the Value is null, the []Value will be set
|
|
// to an empty slice.
|
|
//
|
|
// Future builtin conversions may be added over time.
|
|
//
|
|
// If `val` is unknown, an error will be returned, as unknown values can't be
|
|
// represented in Go's type system. Providers should check Value.IsKnown before
|
|
// calling Value.As.
|
|
func (val Value) As(dst interface{}) error {
|
|
unmarshaler, ok := dst.(ValueConverter)
|
|
if ok {
|
|
return unmarshaler.FromTerraform5Value(val)
|
|
}
|
|
if !val.IsKnown() {
|
|
return fmt.Errorf("unmarshaling unknown values is not supported")
|
|
}
|
|
switch target := dst.(type) {
|
|
case *string:
|
|
if val.IsNull() {
|
|
*target = ""
|
|
return nil
|
|
}
|
|
v, ok := val.value.(string)
|
|
if !ok {
|
|
return fmt.Errorf("can't unmarshal %s into %T, expected string", val.Type(), dst)
|
|
}
|
|
*target = v
|
|
return nil
|
|
case **string:
|
|
if val.IsNull() {
|
|
*target = nil
|
|
return nil
|
|
}
|
|
if *target == nil {
|
|
var s string
|
|
*target = &s
|
|
}
|
|
return val.As(*target)
|
|
case *big.Float:
|
|
if val.IsNull() {
|
|
target.Set(big.NewFloat(0))
|
|
return nil
|
|
}
|
|
v, ok := val.value.(*big.Float)
|
|
if !ok {
|
|
return fmt.Errorf("can't unmarshal %s into %T, expected *big.Float", val.Type(), dst)
|
|
}
|
|
target.Copy(v)
|
|
return nil
|
|
case **big.Float:
|
|
if val.IsNull() {
|
|
*target = nil
|
|
return nil
|
|
}
|
|
if *target == nil {
|
|
*target = big.NewFloat(0)
|
|
}
|
|
return val.As(*target)
|
|
case *bool:
|
|
if val.IsNull() {
|
|
*target = false
|
|
return nil
|
|
}
|
|
v, ok := val.value.(bool)
|
|
if !ok {
|
|
return fmt.Errorf("can't unmarshal %s into %T, expected boolean", val.Type(), dst)
|
|
}
|
|
*target = v
|
|
return nil
|
|
case **bool:
|
|
if val.IsNull() {
|
|
*target = nil
|
|
return nil
|
|
}
|
|
if *target == nil {
|
|
var b bool
|
|
*target = &b
|
|
}
|
|
return val.As(*target)
|
|
case *map[string]Value:
|
|
if val.IsNull() {
|
|
*target = map[string]Value{}
|
|
return nil
|
|
}
|
|
v, ok := val.value.(map[string]Value)
|
|
if !ok {
|
|
return fmt.Errorf("can't unmarshal %s into %T, expected map[string]tftypes.Value", val.Type(), dst)
|
|
}
|
|
*target = v
|
|
return nil
|
|
case **map[string]Value:
|
|
if val.IsNull() {
|
|
*target = nil
|
|
return nil
|
|
}
|
|
if *target == nil {
|
|
m := map[string]Value{}
|
|
*target = &m
|
|
}
|
|
return val.As(*target)
|
|
case *[]Value:
|
|
if val.IsNull() {
|
|
*target = []Value{}
|
|
return nil
|
|
}
|
|
v, ok := val.value.([]Value)
|
|
if !ok {
|
|
return fmt.Errorf("can't unmarshal %s into %T expected []tftypes.Value", val.Type(), dst)
|
|
}
|
|
*target = v
|
|
return nil
|
|
case **[]Value:
|
|
if val.IsNull() {
|
|
*target = nil
|
|
return nil
|
|
}
|
|
if *target == nil {
|
|
l := []Value{}
|
|
*target = &l
|
|
}
|
|
return val.As(*target)
|
|
}
|
|
return fmt.Errorf("can't unmarshal into %T, needs FromTerraform5Value method", dst)
|
|
}
|
|
|
|
// Type returns the Type of the Value.
|
|
func (val Value) Type() Type {
|
|
return val.typ
|
|
}
|
|
|
|
// IsKnown returns true if `val` is known. If `val` is an aggregate type, only
|
|
// the top level of the aggregate type is checked; elements and attributes are
|
|
// not checked.
|
|
func (val Value) IsKnown() bool {
|
|
return val.value != UnknownValue
|
|
}
|
|
|
|
// IsFullyKnown returns true if `val` is known. If `val` is an aggregate type,
|
|
// IsFullyKnown only returns true if all elements and attributes are known, as
|
|
// well.
|
|
func (val Value) IsFullyKnown() bool {
|
|
if !val.IsKnown() {
|
|
return false
|
|
}
|
|
if val.value == nil {
|
|
return true
|
|
}
|
|
switch val.Type().(type) {
|
|
case primitive:
|
|
return true
|
|
case List, Set, Tuple:
|
|
for _, v := range val.value.([]Value) {
|
|
if !v.IsFullyKnown() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case Map, Object:
|
|
for _, v := range val.value.(map[string]Value) {
|
|
if !v.IsFullyKnown() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
panic(fmt.Sprintf("unknown type %T", val.Type()))
|
|
}
|
|
|
|
// IsNull returns true if the Value is null.
|
|
func (val Value) IsNull() bool {
|
|
return val.value == nil
|
|
}
|
|
|
|
// MarshalMsgPack returns a msgpack representation of the Value. This is used
|
|
// for constructing tfprotov5.DynamicValues.
|
|
//
|
|
// Deprecated: this is not meant to be called by third parties. Don't use it.
|
|
func (val Value) MarshalMsgPack(t Type) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
enc := msgpack.NewEncoder(&buf)
|
|
|
|
err := marshalMsgPack(val, t, NewAttributePath(), enc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func unexpectedValueTypeError(p *AttributePath, expected, got interface{}, typ Type) error {
|
|
return p.NewErrorf("unexpected value type %T, %s values must be of type %T", got, typ, expected)
|
|
}
|