github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/configs/configschema/coerce_value.go (about)

     1  package configschema
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/zclconf/go-cty/cty"
     7  	"github.com/zclconf/go-cty/cty/convert"
     8  )
     9  
    10  // CoerceValue attempts to force the given value to conform to the type
    11  // implied by the receiever.
    12  //
    13  // This is useful in situations where a configuration must be derived from
    14  // an already-decoded value. It is always better to decode directly from
    15  // configuration where possible since then source location information is
    16  // still available to produce diagnostics, but in special situations this
    17  // function allows a compatible result to be obtained even if the
    18  // configuration objects are not available.
    19  //
    20  // If the given value cannot be converted to conform to the receiving schema
    21  // then an error is returned describing one of possibly many problems. This
    22  // error may be a cty.PathError indicating a position within the nested
    23  // data structure where the problem applies.
    24  func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
    25  	var path cty.Path
    26  	return b.coerceValue(in, path)
    27  }
    28  
    29  func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
    30  	switch {
    31  	case in.IsNull():
    32  		return cty.NullVal(b.ImpliedType()), nil
    33  	case !in.IsKnown():
    34  		return cty.UnknownVal(b.ImpliedType()), nil
    35  	}
    36  
    37  	ty := in.Type()
    38  	if !ty.IsObjectType() {
    39  		return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
    40  	}
    41  
    42  	for name := range ty.AttributeTypes() {
    43  		if _, defined := b.Attributes[name]; defined {
    44  			continue
    45  		}
    46  		if _, defined := b.BlockTypes[name]; defined {
    47  			continue
    48  		}
    49  		return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
    50  	}
    51  
    52  	attrs := make(map[string]cty.Value)
    53  
    54  	for name, attrS := range b.Attributes {
    55  		var val cty.Value
    56  		switch {
    57  		case ty.HasAttribute(name):
    58  			val = in.GetAttr(name)
    59  		case attrS.Computed || attrS.Optional:
    60  			val = cty.NullVal(attrS.Type)
    61  		default:
    62  			return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
    63  		}
    64  
    65  		val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
    66  		if err != nil {
    67  			return cty.UnknownVal(b.ImpliedType()), err
    68  		}
    69  
    70  		attrs[name] = val
    71  	}
    72  	for typeName, blockS := range b.BlockTypes {
    73  		switch blockS.Nesting {
    74  
    75  		case NestingSingle, NestingGroup:
    76  			switch {
    77  			case ty.HasAttribute(typeName):
    78  				var err error
    79  				val := in.GetAttr(typeName)
    80  				attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
    81  				if err != nil {
    82  					return cty.UnknownVal(b.ImpliedType()), err
    83  				}
    84  			default:
    85  				attrs[typeName] = blockS.EmptyValue()
    86  			}
    87  
    88  		case NestingList:
    89  			switch {
    90  			case ty.HasAttribute(typeName):
    91  				coll := in.GetAttr(typeName)
    92  
    93  				switch {
    94  				case coll.IsNull():
    95  					attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType()))
    96  					continue
    97  				case !coll.IsKnown():
    98  					attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType()))
    99  					continue
   100  				}
   101  
   102  				if !coll.CanIterateElements() {
   103  					return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
   104  				}
   105  				l := coll.LengthInt()
   106  
   107  				if l == 0 {
   108  					attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
   109  					continue
   110  				}
   111  				elems := make([]cty.Value, 0, l)
   112  				{
   113  					path = append(path, cty.GetAttrStep{Name: typeName})
   114  					for it := coll.ElementIterator(); it.Next(); {
   115  						var err error
   116  						idx, val := it.Element()
   117  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
   118  						if err != nil {
   119  							return cty.UnknownVal(b.ImpliedType()), err
   120  						}
   121  						elems = append(elems, val)
   122  					}
   123  				}
   124  				attrs[typeName] = cty.ListVal(elems)
   125  			default:
   126  				attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
   127  			}
   128  
   129  		case NestingSet:
   130  			switch {
   131  			case ty.HasAttribute(typeName):
   132  				coll := in.GetAttr(typeName)
   133  
   134  				switch {
   135  				case coll.IsNull():
   136  					attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
   137  					continue
   138  				case !coll.IsKnown():
   139  					attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
   140  					continue
   141  				}
   142  
   143  				if !coll.CanIterateElements() {
   144  					return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
   145  				}
   146  				l := coll.LengthInt()
   147  
   148  				if l == 0 {
   149  					attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
   150  					continue
   151  				}
   152  				elems := make([]cty.Value, 0, l)
   153  				{
   154  					path = append(path, cty.GetAttrStep{Name: typeName})
   155  					for it := coll.ElementIterator(); it.Next(); {
   156  						var err error
   157  						idx, val := it.Element()
   158  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
   159  						if err != nil {
   160  							return cty.UnknownVal(b.ImpliedType()), err
   161  						}
   162  						elems = append(elems, val)
   163  					}
   164  				}
   165  				attrs[typeName] = cty.SetVal(elems)
   166  			default:
   167  				attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
   168  			}
   169  
   170  		case NestingMap:
   171  			switch {
   172  			case ty.HasAttribute(typeName):
   173  				coll := in.GetAttr(typeName)
   174  
   175  				switch {
   176  				case coll.IsNull():
   177  					attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
   178  					continue
   179  				case !coll.IsKnown():
   180  					attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
   181  					continue
   182  				}
   183  
   184  				if !coll.CanIterateElements() {
   185  					return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
   186  				}
   187  				l := coll.LengthInt()
   188  				if l == 0 {
   189  					attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
   190  					continue
   191  				}
   192  				elems := make(map[string]cty.Value)
   193  				{
   194  					path = append(path, cty.GetAttrStep{Name: typeName})
   195  					for it := coll.ElementIterator(); it.Next(); {
   196  						var err error
   197  						key, val := it.Element()
   198  						if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
   199  							return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
   200  						}
   201  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
   202  						if err != nil {
   203  							return cty.UnknownVal(b.ImpliedType()), err
   204  						}
   205  						elems[key.AsString()] = val
   206  					}
   207  				}
   208  
   209  				// If the attribute values here contain any DynamicPseudoTypes,
   210  				// the concrete type must be an object.
   211  				useObject := false
   212  				switch {
   213  				case coll.Type().IsObjectType():
   214  					useObject = true
   215  				default:
   216  					// It's possible that we were given a map, and need to coerce it to an object
   217  					ety := coll.Type().ElementType()
   218  					for _, v := range elems {
   219  						if !v.Type().Equals(ety) {
   220  							useObject = true
   221  							break
   222  						}
   223  					}
   224  				}
   225  
   226  				if useObject {
   227  					attrs[typeName] = cty.ObjectVal(elems)
   228  				} else {
   229  					attrs[typeName] = cty.MapVal(elems)
   230  				}
   231  			default:
   232  				attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
   233  			}
   234  
   235  		default:
   236  			// should never happen because above is exhaustive
   237  			panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
   238  		}
   239  	}
   240  
   241  	return cty.ObjectVal(attrs), nil
   242  }
   243  
   244  func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
   245  	val, err := convert.Convert(in, a.Type)
   246  	if err != nil {
   247  		return cty.UnknownVal(a.Type), path.NewError(err)
   248  	}
   249  	return val, nil
   250  }