84c9110a24
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.24.1 to 2.26.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.24.1...v2.26.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
263 lines
8.3 KiB
Go
263 lines
8.3 KiB
Go
package convert
|
|
|
|
import (
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// conversion is an internal variant of Conversion that carries around
|
|
// a cty.Path to be used in error responses.
|
|
type conversion func(cty.Value, cty.Path) (cty.Value, error)
|
|
|
|
func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|
conv := getConversionKnown(in, out, unsafe)
|
|
if conv == nil {
|
|
return nil
|
|
}
|
|
|
|
// Wrap the conversion in some standard checks that we don't want to
|
|
// have to repeat in every conversion function.
|
|
var ret conversion
|
|
ret = func(in cty.Value, path cty.Path) (cty.Value, error) {
|
|
if in.IsMarked() {
|
|
// We must unmark during the conversion and then re-apply the
|
|
// same marks to the result.
|
|
in, inMarks := in.Unmark()
|
|
v, err := ret(in, path)
|
|
if v != cty.NilVal {
|
|
v = v.WithMarks(inMarks)
|
|
}
|
|
return v, err
|
|
}
|
|
|
|
if out == cty.DynamicPseudoType {
|
|
// Conversion to DynamicPseudoType always just passes through verbatim.
|
|
return in, nil
|
|
}
|
|
if isKnown, isNull := in.IsKnown(), in.IsNull(); !isKnown || isNull {
|
|
// Avoid constructing unknown or null values with types which
|
|
// include optional attributes. Known or non-null object values
|
|
// will be passed to a conversion function which drops the optional
|
|
// attributes from the type. Unknown and null pass through values
|
|
// must do the same to ensure that homogeneous collections have a
|
|
// single element type.
|
|
out = out.WithoutOptionalAttributesDeep()
|
|
|
|
if !isKnown {
|
|
return prepareUnknownResult(in.Range(), dynamicReplace(in.Type(), out)), nil
|
|
}
|
|
|
|
if isNull {
|
|
// We'll pass through nulls, albeit type converted, and let
|
|
// the caller deal with whatever handling they want to do in
|
|
// case null values are considered valid in some applications.
|
|
return cty.NullVal(dynamicReplace(in.Type(), out)), nil
|
|
}
|
|
}
|
|
|
|
return conv(in, path)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|
switch {
|
|
|
|
case out == cty.DynamicPseudoType:
|
|
// Conversion *to* DynamicPseudoType means that the caller wishes
|
|
// to allow any type in this position, so we'll produce a do-nothing
|
|
// conversion that just passes through the value as-is.
|
|
return dynamicPassthrough
|
|
|
|
case unsafe && in == cty.DynamicPseudoType:
|
|
// Conversion *from* DynamicPseudoType means that we have a value
|
|
// whose type isn't yet known during type checking. For these we will
|
|
// assume that conversion will succeed and deal with any errors that
|
|
// result (which is why we can only do this when "unsafe" is set).
|
|
return dynamicFixup(out)
|
|
|
|
case in.IsPrimitiveType() && out.IsPrimitiveType():
|
|
conv := primitiveConversionsSafe[in][out]
|
|
if conv != nil {
|
|
return conv
|
|
}
|
|
if unsafe {
|
|
return primitiveConversionsUnsafe[in][out]
|
|
}
|
|
return nil
|
|
|
|
case out.IsObjectType() && in.IsObjectType():
|
|
return conversionObjectToObject(in, out, unsafe)
|
|
|
|
case out.IsTupleType() && in.IsTupleType():
|
|
return conversionTupleToTuple(in, out, unsafe)
|
|
|
|
case out.IsListType() && (in.IsListType() || in.IsSetType()):
|
|
inEty := in.ElementType()
|
|
outEty := out.ElementType()
|
|
if inEty.Equals(outEty) {
|
|
// This indicates that we're converting from list to set with
|
|
// the same element type, so we don't need an element converter.
|
|
return conversionCollectionToList(outEty, nil)
|
|
}
|
|
|
|
convEty := getConversion(inEty, outEty, unsafe)
|
|
if convEty == nil {
|
|
return nil
|
|
}
|
|
return conversionCollectionToList(outEty, convEty)
|
|
|
|
case out.IsSetType() && (in.IsListType() || in.IsSetType()):
|
|
if in.IsListType() && !unsafe {
|
|
// Conversion from list to map is unsafe because it will lose
|
|
// information: the ordering will not be preserved, and any
|
|
// duplicate elements will be conflated.
|
|
return nil
|
|
}
|
|
inEty := in.ElementType()
|
|
outEty := out.ElementType()
|
|
convEty := getConversion(inEty, outEty, unsafe)
|
|
if inEty.Equals(outEty) {
|
|
// This indicates that we're converting from set to list with
|
|
// the same element type, so we don't need an element converter.
|
|
return conversionCollectionToSet(outEty, nil)
|
|
}
|
|
|
|
if convEty == nil {
|
|
return nil
|
|
}
|
|
return conversionCollectionToSet(outEty, convEty)
|
|
|
|
case out.IsMapType() && in.IsMapType():
|
|
inEty := in.ElementType()
|
|
outEty := out.ElementType()
|
|
convEty := getConversion(inEty, outEty, unsafe)
|
|
if convEty == nil {
|
|
return nil
|
|
}
|
|
return conversionCollectionToMap(outEty, convEty)
|
|
|
|
case out.IsListType() && in.IsTupleType():
|
|
outEty := out.ElementType()
|
|
return conversionTupleToList(in, outEty, unsafe)
|
|
|
|
case out.IsSetType() && in.IsTupleType():
|
|
outEty := out.ElementType()
|
|
return conversionTupleToSet(in, outEty, unsafe)
|
|
|
|
case out.IsMapType() && in.IsObjectType():
|
|
outEty := out.ElementType()
|
|
return conversionObjectToMap(in, outEty, unsafe)
|
|
|
|
case out.IsObjectType() && in.IsMapType():
|
|
if !unsafe {
|
|
// Converting a map to an object is an "unsafe" conversion,
|
|
// because we don't know if all the map keys will correspond to
|
|
// object attributes.
|
|
return nil
|
|
}
|
|
return conversionMapToObject(in, out, unsafe)
|
|
|
|
case in.IsCapsuleType() || out.IsCapsuleType():
|
|
if !unsafe {
|
|
// Capsule types can only participate in "unsafe" conversions,
|
|
// because we don't know enough about their conversion behaviors
|
|
// to be sure that they will always be safe.
|
|
return nil
|
|
}
|
|
if in.Equals(out) {
|
|
// conversion to self is never allowed
|
|
return nil
|
|
}
|
|
if out.IsCapsuleType() {
|
|
if fn := out.CapsuleOps().ConversionTo; fn != nil {
|
|
return conversionToCapsule(in, out, fn)
|
|
}
|
|
}
|
|
if in.IsCapsuleType() {
|
|
if fn := in.CapsuleOps().ConversionFrom; fn != nil {
|
|
return conversionFromCapsule(in, out, fn)
|
|
}
|
|
}
|
|
// No conversion operation is available, then.
|
|
return nil
|
|
|
|
default:
|
|
return nil
|
|
|
|
}
|
|
}
|
|
|
|
// retConversion wraps a conversion (internal type) so it can be returned
|
|
// as a Conversion (public type).
|
|
func retConversion(conv conversion) Conversion {
|
|
if conv == nil {
|
|
return nil
|
|
}
|
|
|
|
return func(in cty.Value) (cty.Value, error) {
|
|
return conv(in, cty.Path(nil))
|
|
}
|
|
}
|
|
|
|
// prepareUnknownResult can apply value refinements to a returned unknown value
|
|
// in certain cases where characteristics of the source value or type can
|
|
// transfer into range constraints on the result value.
|
|
func prepareUnknownResult(sourceRange cty.ValueRange, targetTy cty.Type) cty.Value {
|
|
sourceTy := sourceRange.TypeConstraint()
|
|
|
|
ret := cty.UnknownVal(targetTy)
|
|
if sourceRange.DefinitelyNotNull() {
|
|
ret = ret.RefineNotNull()
|
|
}
|
|
|
|
switch {
|
|
case sourceTy.IsObjectType() && targetTy.IsMapType():
|
|
// A map built from an object type always has the same number of
|
|
// elements as the source type has attributes.
|
|
return ret.Refine().CollectionLength(len(sourceTy.AttributeTypes())).NewValue()
|
|
case sourceTy.IsTupleType() && targetTy.IsListType():
|
|
// A list built from a typle type always has the same number of
|
|
// elements as the source type has elements.
|
|
return ret.Refine().CollectionLength(sourceTy.Length()).NewValue()
|
|
case sourceTy.IsTupleType() && targetTy.IsSetType():
|
|
// When building a set from a tuple type we can't exactly constrain
|
|
// the length because some elements might coalesce, but we can
|
|
// guarantee an upper limit. We can also guarantee at least one
|
|
// element if the tuple isn't empty.
|
|
switch l := sourceTy.Length(); l {
|
|
case 0, 1:
|
|
return ret.Refine().CollectionLength(l).NewValue()
|
|
default:
|
|
return ret.Refine().
|
|
CollectionLengthLowerBound(1).
|
|
CollectionLengthUpperBound(sourceTy.Length()).
|
|
NewValue()
|
|
}
|
|
case sourceTy.IsCollectionType() && targetTy.IsCollectionType():
|
|
// NOTE: We only reach this function if there is an available
|
|
// conversion between the source and target type, so we don't
|
|
// need to repeat element type compatibility checks and such here.
|
|
//
|
|
// If the source value already has a refined length then we'll
|
|
// transfer those refinements to the result, because conversion
|
|
// does not change length (aside from set element coalescing).
|
|
b := ret.Refine()
|
|
if targetTy.IsSetType() {
|
|
if sourceRange.LengthLowerBound() > 0 {
|
|
// If the source has at least one element then the result
|
|
// must always have at least one too, because value coalescing
|
|
// cannot totally empty the set.
|
|
b = b.CollectionLengthLowerBound(1)
|
|
}
|
|
} else {
|
|
b = b.CollectionLengthLowerBound(sourceRange.LengthLowerBound())
|
|
}
|
|
b = b.CollectionLengthUpperBound(sourceRange.LengthUpperBound())
|
|
return b.NewValue()
|
|
default:
|
|
return ret
|
|
}
|
|
|
|
}
|