kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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  	convType := b.specType()
    31  	impliedType := convType.WithoutOptionalAttributesDeep()
    32  
    33  	switch {
    34  	case in.IsNull():
    35  		return cty.NullVal(impliedType), nil
    36  	case !in.IsKnown():
    37  		return cty.UnknownVal(impliedType), nil
    38  	}
    39  
    40  	ty := in.Type()
    41  	if !ty.IsObjectType() {
    42  		return cty.UnknownVal(impliedType), path.NewErrorf("an object is required")
    43  	}
    44  
    45  	for name := range ty.AttributeTypes() {
    46  		if _, defined := b.Attributes[name]; defined {
    47  			continue
    48  		}
    49  		if _, defined := b.BlockTypes[name]; defined {
    50  			continue
    51  		}
    52  		return cty.UnknownVal(impliedType), path.NewErrorf("unexpected attribute %q", name)
    53  	}
    54  
    55  	attrs := make(map[string]cty.Value)
    56  
    57  	for name, attrS := range b.Attributes {
    58  		attrType := impliedType.AttributeType(name)
    59  		attrConvType := convType.AttributeType(name)
    60  
    61  		var val cty.Value
    62  		switch {
    63  		case ty.HasAttribute(name):
    64  			val = in.GetAttr(name)
    65  		case attrS.Computed || attrS.Optional:
    66  			val = cty.NullVal(attrType)
    67  		default:
    68  			return cty.UnknownVal(impliedType), path.NewErrorf("attribute %q is required", name)
    69  		}
    70  
    71  		val, err := convert.Convert(val, attrConvType)
    72  		if err != nil {
    73  			return cty.UnknownVal(impliedType), append(path, cty.GetAttrStep{Name: name}).NewError(err)
    74  		}
    75  		attrs[name] = val
    76  	}
    77  
    78  	for typeName, blockS := range b.BlockTypes {
    79  		switch blockS.Nesting {
    80  
    81  		case NestingSingle, NestingGroup:
    82  			switch {
    83  			case ty.HasAttribute(typeName):
    84  				var err error
    85  				val := in.GetAttr(typeName)
    86  				attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
    87  				if err != nil {
    88  					return cty.UnknownVal(impliedType), err
    89  				}
    90  			default:
    91  				attrs[typeName] = blockS.EmptyValue()
    92  			}
    93  
    94  		case NestingList:
    95  			switch {
    96  			case ty.HasAttribute(typeName):
    97  				coll := in.GetAttr(typeName)
    98  
    99  				switch {
   100  				case coll.IsNull():
   101  					attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType()))
   102  					continue
   103  				case !coll.IsKnown():
   104  					attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType()))
   105  					continue
   106  				}
   107  
   108  				if !coll.CanIterateElements() {
   109  					return cty.UnknownVal(impliedType), path.NewErrorf("must be a list")
   110  				}
   111  				l := coll.LengthInt()
   112  
   113  				if l == 0 {
   114  					attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
   115  					continue
   116  				}
   117  				elems := make([]cty.Value, 0, l)
   118  				{
   119  					path = append(path, cty.GetAttrStep{Name: typeName})
   120  					for it := coll.ElementIterator(); it.Next(); {
   121  						var err error
   122  						idx, val := it.Element()
   123  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
   124  						if err != nil {
   125  							return cty.UnknownVal(impliedType), err
   126  						}
   127  						elems = append(elems, val)
   128  					}
   129  				}
   130  				attrs[typeName] = cty.ListVal(elems)
   131  			default:
   132  				attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
   133  			}
   134  
   135  		case NestingSet:
   136  			switch {
   137  			case ty.HasAttribute(typeName):
   138  				coll := in.GetAttr(typeName)
   139  
   140  				switch {
   141  				case coll.IsNull():
   142  					attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
   143  					continue
   144  				case !coll.IsKnown():
   145  					attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
   146  					continue
   147  				}
   148  
   149  				if !coll.CanIterateElements() {
   150  					return cty.UnknownVal(impliedType), path.NewErrorf("must be a set")
   151  				}
   152  				l := coll.LengthInt()
   153  
   154  				if l == 0 {
   155  					attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
   156  					continue
   157  				}
   158  				elems := make([]cty.Value, 0, l)
   159  				{
   160  					path = append(path, cty.GetAttrStep{Name: typeName})
   161  					for it := coll.ElementIterator(); it.Next(); {
   162  						var err error
   163  						idx, val := it.Element()
   164  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
   165  						if err != nil {
   166  							return cty.UnknownVal(impliedType), err
   167  						}
   168  						elems = append(elems, val)
   169  					}
   170  				}
   171  				attrs[typeName] = cty.SetVal(elems)
   172  			default:
   173  				attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
   174  			}
   175  
   176  		case NestingMap:
   177  			switch {
   178  			case ty.HasAttribute(typeName):
   179  				coll := in.GetAttr(typeName)
   180  
   181  				switch {
   182  				case coll.IsNull():
   183  					attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
   184  					continue
   185  				case !coll.IsKnown():
   186  					attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
   187  					continue
   188  				}
   189  
   190  				if !coll.CanIterateElements() {
   191  					return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
   192  				}
   193  				l := coll.LengthInt()
   194  				if l == 0 {
   195  					attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
   196  					continue
   197  				}
   198  				elems := make(map[string]cty.Value)
   199  				{
   200  					path = append(path, cty.GetAttrStep{Name: typeName})
   201  					for it := coll.ElementIterator(); it.Next(); {
   202  						var err error
   203  						key, val := it.Element()
   204  						if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
   205  							return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
   206  						}
   207  						val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
   208  						if err != nil {
   209  							return cty.UnknownVal(impliedType), err
   210  						}
   211  						elems[key.AsString()] = val
   212  					}
   213  				}
   214  
   215  				// If the attribute values here contain any DynamicPseudoTypes,
   216  				// the concrete type must be an object.
   217  				useObject := false
   218  				switch {
   219  				case coll.Type().IsObjectType():
   220  					useObject = true
   221  				default:
   222  					// It's possible that we were given a map, and need to coerce it to an object
   223  					ety := coll.Type().ElementType()
   224  					for _, v := range elems {
   225  						if !v.Type().Equals(ety) {
   226  							useObject = true
   227  							break
   228  						}
   229  					}
   230  				}
   231  
   232  				if useObject {
   233  					attrs[typeName] = cty.ObjectVal(elems)
   234  				} else {
   235  					attrs[typeName] = cty.MapVal(elems)
   236  				}
   237  			default:
   238  				attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
   239  			}
   240  
   241  		default:
   242  			// should never happen because above is exhaustive
   243  			panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
   244  		}
   245  	}
   246  
   247  	return cty.ObjectVal(attrs), nil
   248  }