1package convert 2 3import ( 4 "github.com/zclconf/go-cty/cty" 5) 6 7// conversionCollectionToList returns a conversion that will apply the given 8// conversion to all of the elements of a collection (something that supports 9// ForEachElement and LengthInt) and then returns the result as a list. 10// 11// "conv" can be nil if the elements are expected to already be of the 12// correct type and just need to be re-wrapped into a list. (For example, 13// if we're converting from a set into a list of the same element type.) 14func conversionCollectionToList(ety cty.Type, conv conversion) conversion { 15 return func(val cty.Value, path cty.Path) (cty.Value, error) { 16 if !val.Length().IsKnown() { 17 // If the input collection has an unknown length (which is true 18 // for a set containing unknown values) then our result must be 19 // an unknown list, because we can't predict how many elements 20 // the resulting list should have. 21 return cty.UnknownVal(cty.List(val.Type().ElementType())), nil 22 } 23 24 elems := make([]cty.Value, 0, val.LengthInt()) 25 i := int64(0) 26 elemPath := append(path.Copy(), nil) 27 it := val.ElementIterator() 28 for it.Next() { 29 _, val := it.Element() 30 var err error 31 32 elemPath[len(elemPath)-1] = cty.IndexStep{ 33 Key: cty.NumberIntVal(i), 34 } 35 36 if conv != nil { 37 val, err = conv(val, elemPath) 38 if err != nil { 39 return cty.NilVal, err 40 } 41 } 42 elems = append(elems, val) 43 44 i++ 45 } 46 47 if len(elems) == 0 { 48 // Prefer a concrete type over a dynamic type when returning an 49 // empty list 50 if ety == cty.DynamicPseudoType { 51 return cty.ListValEmpty(val.Type().ElementType()), nil 52 } 53 return cty.ListValEmpty(ety), nil 54 } 55 56 if !cty.CanListVal(elems) { 57 return cty.NilVal, path.NewErrorf("element types must all match for conversion to list") 58 } 59 60 return cty.ListVal(elems), nil 61 } 62} 63 64// conversionCollectionToSet returns a conversion that will apply the given 65// conversion to all of the elements of a collection (something that supports 66// ForEachElement and LengthInt) and then returns the result as a set. 67// 68// "conv" can be nil if the elements are expected to already be of the 69// correct type and just need to be re-wrapped into a set. (For example, 70// if we're converting from a list into a set of the same element type.) 71func conversionCollectionToSet(ety cty.Type, conv conversion) conversion { 72 return func(val cty.Value, path cty.Path) (cty.Value, error) { 73 elems := make([]cty.Value, 0, val.LengthInt()) 74 i := int64(0) 75 elemPath := append(path.Copy(), nil) 76 it := val.ElementIterator() 77 for it.Next() { 78 _, val := it.Element() 79 var err error 80 81 elemPath[len(elemPath)-1] = cty.IndexStep{ 82 Key: cty.NumberIntVal(i), 83 } 84 85 if conv != nil { 86 val, err = conv(val, elemPath) 87 if err != nil { 88 return cty.NilVal, err 89 } 90 } 91 elems = append(elems, val) 92 93 i++ 94 } 95 96 if len(elems) == 0 { 97 // Prefer a concrete type over a dynamic type when returning an 98 // empty set 99 if ety == cty.DynamicPseudoType { 100 return cty.SetValEmpty(val.Type().ElementType()), nil 101 } 102 return cty.SetValEmpty(ety), nil 103 } 104 105 if !cty.CanSetVal(elems) { 106 return cty.NilVal, path.NewErrorf("element types must all match for conversion to set") 107 } 108 109 return cty.SetVal(elems), nil 110 } 111} 112 113// conversionCollectionToMap returns a conversion that will apply the given 114// conversion to all of the elements of a collection (something that supports 115// ForEachElement and LengthInt) and then returns the result as a map. 116// 117// "conv" can be nil if the elements are expected to already be of the 118// correct type and just need to be re-wrapped into a map. 119func conversionCollectionToMap(ety cty.Type, conv conversion) conversion { 120 return func(val cty.Value, path cty.Path) (cty.Value, error) { 121 elems := make(map[string]cty.Value, 0) 122 elemPath := append(path.Copy(), nil) 123 it := val.ElementIterator() 124 for it.Next() { 125 key, val := it.Element() 126 var err error 127 128 elemPath[len(elemPath)-1] = cty.IndexStep{ 129 Key: key, 130 } 131 132 keyStr, err := Convert(key, cty.String) 133 if err != nil { 134 // Should never happen, because keys can only be numbers or 135 // strings and both can convert to string. 136 return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName()) 137 } 138 139 if conv != nil { 140 val, err = conv(val, elemPath) 141 if err != nil { 142 return cty.NilVal, err 143 } 144 } 145 146 elems[keyStr.AsString()] = val 147 } 148 149 if len(elems) == 0 { 150 // Prefer a concrete type over a dynamic type when returning an 151 // empty map 152 if ety == cty.DynamicPseudoType { 153 return cty.MapValEmpty(val.Type().ElementType()), nil 154 } 155 return cty.MapValEmpty(ety), nil 156 } 157 158 if ety.IsCollectionType() || ety.IsObjectType() { 159 var err error 160 if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil { 161 return cty.NilVal, err 162 } 163 } 164 165 if !cty.CanMapVal(elems) { 166 return cty.NilVal, path.NewErrorf("element types must all match for conversion to map") 167 } 168 169 return cty.MapVal(elems), nil 170 } 171} 172 173// conversionTupleToSet returns a conversion that will take a value of the 174// given tuple type and return a set of the given element type. 175// 176// Will panic if the given tupleType isn't actually a tuple type. 177func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conversion { 178 tupleEtys := tupleType.TupleElementTypes() 179 180 if len(tupleEtys) == 0 { 181 // Empty tuple short-circuit 182 return func(val cty.Value, path cty.Path) (cty.Value, error) { 183 return cty.SetValEmpty(setEty), nil 184 } 185 } 186 187 if setEty == cty.DynamicPseudoType { 188 // This is a special case where the caller wants us to find 189 // a suitable single type that all elements can convert to, if 190 // possible. 191 setEty, _ = unify(tupleEtys, unsafe) 192 if setEty == cty.NilType { 193 return nil 194 } 195 196 // If the set element type after unification is still the dynamic 197 // type, the only way this can result in a valid set is if all values 198 // are of dynamic type 199 if setEty == cty.DynamicPseudoType { 200 for _, tupleEty := range tupleEtys { 201 if !tupleEty.Equals(cty.DynamicPseudoType) { 202 return nil 203 } 204 } 205 } 206 } 207 208 elemConvs := make([]conversion, len(tupleEtys)) 209 for i, tupleEty := range tupleEtys { 210 if tupleEty.Equals(setEty) { 211 // no conversion required 212 continue 213 } 214 215 elemConvs[i] = getConversion(tupleEty, setEty, unsafe) 216 if elemConvs[i] == nil { 217 // If any of our element conversions are impossible, then the our 218 // whole conversion is impossible. 219 return nil 220 } 221 } 222 223 // If we fall out here then a conversion is possible, using the 224 // element conversions in elemConvs 225 return func(val cty.Value, path cty.Path) (cty.Value, error) { 226 elems := make([]cty.Value, 0, len(elemConvs)) 227 elemPath := append(path.Copy(), nil) 228 i := int64(0) 229 it := val.ElementIterator() 230 for it.Next() { 231 _, val := it.Element() 232 var err error 233 234 elemPath[len(elemPath)-1] = cty.IndexStep{ 235 Key: cty.NumberIntVal(i), 236 } 237 238 conv := elemConvs[i] 239 if conv != nil { 240 val, err = conv(val, elemPath) 241 if err != nil { 242 return cty.NilVal, err 243 } 244 } 245 elems = append(elems, val) 246 247 i++ 248 } 249 250 if !cty.CanSetVal(elems) { 251 return cty.NilVal, path.NewErrorf("element types must all match for conversion to set") 252 } 253 254 return cty.SetVal(elems), nil 255 } 256} 257 258// conversionTupleToList returns a conversion that will take a value of the 259// given tuple type and return a list of the given element type. 260// 261// Will panic if the given tupleType isn't actually a tuple type. 262func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion { 263 tupleEtys := tupleType.TupleElementTypes() 264 265 if len(tupleEtys) == 0 { 266 // Empty tuple short-circuit 267 return func(val cty.Value, path cty.Path) (cty.Value, error) { 268 return cty.ListValEmpty(listEty), nil 269 } 270 } 271 272 if listEty == cty.DynamicPseudoType { 273 // This is a special case where the caller wants us to find 274 // a suitable single type that all elements can convert to, if 275 // possible. 276 listEty, _ = unify(tupleEtys, unsafe) 277 if listEty == cty.NilType { 278 return nil 279 } 280 281 // If the list element type after unification is still the dynamic 282 // type, the only way this can result in a valid list is if all values 283 // are of dynamic type 284 if listEty == cty.DynamicPseudoType { 285 for _, tupleEty := range tupleEtys { 286 if !tupleEty.Equals(cty.DynamicPseudoType) { 287 return nil 288 } 289 } 290 } 291 } 292 293 elemConvs := make([]conversion, len(tupleEtys)) 294 for i, tupleEty := range tupleEtys { 295 if tupleEty.Equals(listEty) { 296 // no conversion required 297 continue 298 } 299 300 elemConvs[i] = getConversion(tupleEty, listEty, unsafe) 301 if elemConvs[i] == nil { 302 // If any of our element conversions are impossible, then the our 303 // whole conversion is impossible. 304 return nil 305 } 306 } 307 308 // If we fall out here then a conversion is possible, using the 309 // element conversions in elemConvs 310 return func(val cty.Value, path cty.Path) (cty.Value, error) { 311 elems := make([]cty.Value, 0, len(elemConvs)) 312 elemTys := make([]cty.Type, 0, len(elems)) 313 elemPath := append(path.Copy(), nil) 314 i := int64(0) 315 it := val.ElementIterator() 316 for it.Next() { 317 _, val := it.Element() 318 var err error 319 320 elemPath[len(elemPath)-1] = cty.IndexStep{ 321 Key: cty.NumberIntVal(i), 322 } 323 324 conv := elemConvs[i] 325 if conv != nil { 326 val, err = conv(val, elemPath) 327 if err != nil { 328 return cty.NilVal, err 329 } 330 } 331 elems = append(elems, val) 332 elemTys = append(elemTys, val.Type()) 333 334 i++ 335 } 336 337 elems, err := conversionUnifyListElements(elems, elemPath, unsafe) 338 if err != nil { 339 return cty.NilVal, err 340 } 341 342 if !cty.CanListVal(elems) { 343 return cty.NilVal, path.NewErrorf("element types must all match for conversion to list") 344 } 345 346 return cty.ListVal(elems), nil 347 } 348} 349 350// conversionObjectToMap returns a conversion that will take a value of the 351// given object type and return a map of the given element type. 352// 353// Will panic if the given objectType isn't actually an object type. 354func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion { 355 objectAtys := objectType.AttributeTypes() 356 357 if len(objectAtys) == 0 { 358 // Empty object short-circuit 359 return func(val cty.Value, path cty.Path) (cty.Value, error) { 360 return cty.MapValEmpty(mapEty), nil 361 } 362 } 363 364 if mapEty == cty.DynamicPseudoType { 365 // This is a special case where the caller wants us to find 366 // a suitable single type that all elements can convert to, if 367 // possible. 368 objectAtysList := make([]cty.Type, 0, len(objectAtys)) 369 for _, aty := range objectAtys { 370 objectAtysList = append(objectAtysList, aty) 371 } 372 mapEty, _ = unify(objectAtysList, unsafe) 373 if mapEty == cty.NilType { 374 return nil 375 } 376 } 377 378 elemConvs := make(map[string]conversion, len(objectAtys)) 379 for name, objectAty := range objectAtys { 380 if objectAty.Equals(mapEty) { 381 // no conversion required 382 continue 383 } 384 385 elemConvs[name] = getConversion(objectAty, mapEty, unsafe) 386 if elemConvs[name] == nil { 387 // If any of our element conversions are impossible, then the our 388 // whole conversion is impossible. 389 return nil 390 } 391 } 392 393 // If we fall out here then a conversion is possible, using the 394 // element conversions in elemConvs 395 return func(val cty.Value, path cty.Path) (cty.Value, error) { 396 elems := make(map[string]cty.Value, len(elemConvs)) 397 elemPath := append(path.Copy(), nil) 398 it := val.ElementIterator() 399 for it.Next() { 400 name, val := it.Element() 401 var err error 402 403 elemPath[len(elemPath)-1] = cty.IndexStep{ 404 Key: name, 405 } 406 407 conv := elemConvs[name.AsString()] 408 if conv != nil { 409 val, err = conv(val, elemPath) 410 if err != nil { 411 return cty.NilVal, err 412 } 413 } 414 elems[name.AsString()] = val 415 } 416 417 if mapEty.IsCollectionType() || mapEty.IsObjectType() { 418 var err error 419 if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil { 420 return cty.NilVal, err 421 } 422 } 423 424 if !cty.CanMapVal(elems) { 425 return cty.NilVal, path.NewErrorf("attribute types must all match for conversion to map") 426 } 427 428 return cty.MapVal(elems), nil 429 } 430} 431 432// conversionMapToObject returns a conversion that will take a value of the 433// given map type and return an object of the given type. The object attribute 434// types must all be compatible with the map element type. 435// 436// Will panic if the given mapType and objType are not maps and objects 437// respectively. 438func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion { 439 objectAtys := objType.AttributeTypes() 440 mapEty := mapType.ElementType() 441 442 elemConvs := make(map[string]conversion, len(objectAtys)) 443 for name, objectAty := range objectAtys { 444 if objectAty.Equals(mapEty) { 445 // no conversion required 446 continue 447 } 448 449 elemConvs[name] = getConversion(mapEty, objectAty, unsafe) 450 if elemConvs[name] == nil { 451 // If any of our element conversions are impossible, then the our 452 // whole conversion is impossible. 453 return nil 454 } 455 } 456 457 // If we fall out here then a conversion is possible, using the 458 // element conversions in elemConvs 459 return func(val cty.Value, path cty.Path) (cty.Value, error) { 460 elems := make(map[string]cty.Value, len(elemConvs)) 461 elemPath := append(path.Copy(), nil) 462 it := val.ElementIterator() 463 for it.Next() { 464 name, val := it.Element() 465 466 // if there is no corresponding attribute, we skip this key 467 if _, ok := objectAtys[name.AsString()]; !ok { 468 continue 469 } 470 471 var err error 472 473 elemPath[len(elemPath)-1] = cty.IndexStep{ 474 Key: name, 475 } 476 477 conv := elemConvs[name.AsString()] 478 if conv != nil { 479 val, err = conv(val, elemPath) 480 if err != nil { 481 return cty.NilVal, err 482 } 483 } 484 485 elems[name.AsString()] = val 486 } 487 488 for name, aty := range objectAtys { 489 if _, exists := elems[name]; !exists { 490 if optional := objType.AttributeOptional(name); optional { 491 elems[name] = cty.NullVal(aty) 492 } else { 493 return cty.NilVal, path.NewErrorf("map has no element for required attribute %q", name) 494 } 495 } 496 } 497 498 return cty.ObjectVal(elems), nil 499 } 500} 501 502func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) { 503 elemTypes := make([]cty.Type, 0, len(elems)) 504 for _, elem := range elems { 505 elemTypes = append(elemTypes, elem.Type()) 506 } 507 unifiedType, _ := unify(elemTypes, unsafe) 508 if unifiedType == cty.NilType { 509 return nil, path.NewErrorf("cannot find a common base type for all elements") 510 } 511 512 unifiedElems := make(map[string]cty.Value) 513 elemPath := append(path.Copy(), nil) 514 515 for name, elem := range elems { 516 if elem.Type().Equals(unifiedType) { 517 unifiedElems[name] = elem 518 continue 519 } 520 conv := getConversion(elem.Type(), unifiedType, unsafe) 521 if conv == nil { 522 } 523 elemPath[len(elemPath)-1] = cty.IndexStep{ 524 Key: cty.StringVal(name), 525 } 526 val, err := conv(elem, elemPath) 527 if err != nil { 528 return nil, err 529 } 530 unifiedElems[name] = val 531 } 532 533 return unifiedElems, nil 534} 535 536func conversionUnifyListElements(elems []cty.Value, path cty.Path, unsafe bool) ([]cty.Value, error) { 537 elemTypes := make([]cty.Type, len(elems)) 538 for i, elem := range elems { 539 elemTypes[i] = elem.Type() 540 } 541 unifiedType, _ := unify(elemTypes, unsafe) 542 if unifiedType == cty.NilType { 543 return nil, path.NewErrorf("cannot find a common base type for all elements") 544 } 545 546 ret := make([]cty.Value, len(elems)) 547 elemPath := append(path.Copy(), nil) 548 549 for i, elem := range elems { 550 if elem.Type().Equals(unifiedType) { 551 ret[i] = elem 552 continue 553 } 554 conv := getConversion(elem.Type(), unifiedType, unsafe) 555 if conv == nil { 556 } 557 elemPath[len(elemPath)-1] = cty.IndexStep{ 558 Key: cty.NumberIntVal(int64(i)), 559 } 560 val, err := conv(elem, elemPath) 561 if err != nil { 562 return nil, err 563 } 564 ret[i] = val 565 } 566 567 return ret, nil 568} 569