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 }