github.com/opentofu/opentofu@v1.7.1/internal/plans/dynamic_value.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package plans 7 8 import ( 9 "github.com/zclconf/go-cty/cty" 10 ctymsgpack "github.com/zclconf/go-cty/cty/msgpack" 11 ) 12 13 // DynamicValue is the representation in the plan of a value whose type cannot 14 // be determined at compile time, such as because it comes from a schema 15 // defined in a plugin. 16 // 17 // This type is used as an indirection so that the overall plan structure can 18 // be decoded without schema available, and then the dynamic values accessed 19 // at a later time once the appropriate schema has been determined. 20 // 21 // Internally, DynamicValue is a serialized version of a cty.Value created 22 // against a particular type constraint. Callers should not access directly 23 // the serialized form, whose format may change in future. Values of this 24 // type must always be created by calling NewDynamicValue. 25 // 26 // The zero value of DynamicValue is nil, and represents the absence of a 27 // value within the Go type system. This is distinct from a cty.NullVal 28 // result, which represents the absence of a value within the cty type system. 29 type DynamicValue []byte 30 31 // NewDynamicValue creates a DynamicValue by serializing the given value 32 // against the given type constraint. The value must conform to the type 33 // constraint, or the result is undefined. 34 // 35 // If the value to be encoded has no predefined schema (for example, for 36 // module output values and input variables), set the type constraint to 37 // cty.DynamicPseudoType in order to save type information as part of the 38 // value, and then also pass cty.DynamicPseudoType to method Decode to recover 39 // the original value. 40 // 41 // cty.NilVal can be used to represent the absence of a value, but callers 42 // must be careful to distinguish values that are absent at the Go layer 43 // (cty.NilVal) vs. values that are absent at the cty layer (cty.NullVal 44 // results). 45 func NewDynamicValue(val cty.Value, ty cty.Type) (DynamicValue, error) { 46 // If we're given cty.NilVal (the zero value of cty.Value, which is 47 // distinct from a typed null value created by cty.NullVal) then we'll 48 // assume the caller is trying to represent the _absence_ of a value, 49 // and so we'll return a nil DynamicValue. 50 if val == cty.NilVal { 51 return DynamicValue(nil), nil 52 } 53 54 // Currently our internal encoding is msgpack, via ctymsgpack. 55 buf, err := ctymsgpack.Marshal(val, ty) 56 if err != nil { 57 return nil, err 58 } 59 60 return DynamicValue(buf), nil 61 } 62 63 // Decode retrieves the effective value from the receiever by interpreting the 64 // serialized form against the given type constraint. For correct results, 65 // the type constraint must match (or be consistent with) the one that was 66 // used to create the receiver. 67 // 68 // A nil DynamicValue decodes to cty.NilVal, which is not a valid value and 69 // instead represents the absense of a value. 70 func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) { 71 if v == nil { 72 return cty.NilVal, nil 73 } 74 75 return ctymsgpack.Unmarshal([]byte(v), ty) 76 } 77 78 // ImpliedType returns the type implied by the serialized structure of the 79 // receiving value. 80 // 81 // This will not necessarily be exactly the type that was given when the 82 // value was encoded, and in particular must not be used for values that 83 // were encoded with their static type given as cty.DynamicPseudoType. 84 // It is however safe to use this method for values that were encoded using 85 // their runtime type as the conforming type, with the result being 86 // semantically equivalent but with all lists and sets represented as tuples, 87 // and maps as objects, due to ambiguities of the serialization. 88 func (v DynamicValue) ImpliedType() (cty.Type, error) { 89 return ctymsgpack.ImpliedType([]byte(v)) 90 } 91 92 // Copy produces a copy of the receiver with a distinct backing array. 93 func (v DynamicValue) Copy() DynamicValue { 94 if v == nil { 95 return nil 96 } 97 98 ret := make(DynamicValue, len(v)) 99 copy(ret, v) 100 return ret 101 }