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  }