package plans import ( "github.com/zclconf/go-cty/cty" ctymsgpack "github.com/zclconf/go-cty/cty/msgpack" ) // DynamicValue is the representation in the plan of a value whose type cannot // be determined at compile time, such as because it comes from a schema // defined in a plugin. // // This type is used as an indirection so that the overall plan structure can // be decoded without schema available, and then the dynamic values accessed // at a later time once the appropriate schema has been determined. // // Internally, DynamicValue is a serialized version of a cty.Value created // against a particular type constraint. Callers should not access directly // the serialized form, whose format may change in future. Values of this // type must always be created by calling NewDynamicValue. // // The zero value of DynamicValue is nil, and represents the absense of a // value within the Go type system. This is distinct from a cty.NullVal // result, which represents the absense of a value within the cty type system. type DynamicValue []byte // NewDynamicValue creates a DynamicValue by serializing the given value // against the given type constraint. The value must conform to the type // constraint, or the result is undefined. // // If the value to be encoded has no predefined schema (for example, for // module output values and input variables), set the type constraint to // cty.DynamicPseudoType in order to save type information as part of the // value, and then also pass cty.DynamicPseudoType to method Decode to recover // the original value. // // cty.NilVal can be used to represent the absense of a value, but callers // must be careful to distinguish values that are absent at the Go layer // (cty.NilVal) vs. values that are absent at the cty layer (cty.NullVal // results). func NewDynamicValue(val cty.Value, ty cty.Type) (DynamicValue, error) { // If we're given cty.NilVal (the zero value of cty.Value, which is // distinct from a typed null value created by cty.NullVal) then we'll // assume the caller is trying to represent the _absense_ of a value, // and so we'll return a nil DynamicValue. if val == cty.NilVal { return DynamicValue(nil), nil } // Currently our internal encoding is msgpack, via ctymsgpack. buf, err := ctymsgpack.Marshal(val, ty) if err != nil { return nil, err } return DynamicValue(buf), nil } // Decode retrieves the effective value from the receiever by interpreting the // serialized form against the given type constraint. For correct results, // the type constraint must match (or be consistent with) the one that was // used to create the receiver. // // A nil DynamicValue decodes to cty.NilVal, which is not a valid value and // instead represents the absense of a value. func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) { if v == nil { return cty.NilVal, nil } return ctymsgpack.Unmarshal([]byte(v), ty) } // ImpliedType returns the type implied by the serialized structure of the // receiving value. // // This will not necessarily be exactly the type that was given when the // value was encoded, and in particular must not be used for values that // were encoded with their static type given as cty.DynamicPseudoType. // It is however safe to use this method for values that were encoded using // their runtime type as the conforming type, with the result being // semantically equivalent but with all lists and sets represented as tuples, // and maps as objects, due to ambiguities of the serialization. func (v DynamicValue) ImpliedType() (cty.Type, error) { return ctymsgpack.ImpliedType([]byte(v)) } // Copy produces a copy of the receiver with a distinct backing array. func (v DynamicValue) Copy() DynamicValue { if len(v) == 0 { return nil } ret := make(DynamicValue, len(v)) copy(ret, v) return ret }