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 elems := make([]cty.Value, 0, val.LengthInt()) 17 i := int64(0) 18 path = append(path, nil) 19 it := val.ElementIterator() 20 for it.Next() { 21 _, val := it.Element() 22 var err error 23 24 path[len(path)-1] = cty.IndexStep{ 25 Key: cty.NumberIntVal(i), 26 } 27 28 if conv != nil { 29 val, err = conv(val, path) 30 if err != nil { 31 return cty.NilVal, err 32 } 33 } 34 elems = append(elems, val) 35 36 i++ 37 } 38 39 if len(elems) == 0 { 40 return cty.ListValEmpty(ety), nil 41 } 42 43 return cty.ListVal(elems), nil 44 } 45} 46 47// conversionCollectionToSet returns a conversion that will apply the given 48// conversion to all of the elements of a collection (something that supports 49// ForEachElement and LengthInt) and then returns the result as a set. 50// 51// "conv" can be nil if the elements are expected to already be of the 52// correct type and just need to be re-wrapped into a set. (For example, 53// if we're converting from a list into a set of the same element type.) 54func conversionCollectionToSet(ety cty.Type, conv conversion) conversion { 55 return func(val cty.Value, path cty.Path) (cty.Value, error) { 56 elems := make([]cty.Value, 0, val.LengthInt()) 57 i := int64(0) 58 path = append(path, nil) 59 it := val.ElementIterator() 60 for it.Next() { 61 _, val := it.Element() 62 var err error 63 64 path[len(path)-1] = cty.IndexStep{ 65 Key: cty.NumberIntVal(i), 66 } 67 68 if conv != nil { 69 val, err = conv(val, path) 70 if err != nil { 71 return cty.NilVal, err 72 } 73 } 74 elems = append(elems, val) 75 76 i++ 77 } 78 79 if len(elems) == 0 { 80 return cty.SetValEmpty(ety), nil 81 } 82 83 return cty.SetVal(elems), nil 84 } 85} 86 87// conversionCollectionToMap returns a conversion that will apply the given 88// conversion to all of the elements of a collection (something that supports 89// ForEachElement and LengthInt) and then returns the result as a map. 90// 91// "conv" can be nil if the elements are expected to already be of the 92// correct type and just need to be re-wrapped into a map. 93func conversionCollectionToMap(ety cty.Type, conv conversion) conversion { 94 return func(val cty.Value, path cty.Path) (cty.Value, error) { 95 elems := make(map[string]cty.Value, 0) 96 path = append(path, nil) 97 it := val.ElementIterator() 98 for it.Next() { 99 key, val := it.Element() 100 var err error 101 102 path[len(path)-1] = cty.IndexStep{ 103 Key: key, 104 } 105 106 keyStr, err := Convert(key, cty.String) 107 if err != nil { 108 // Should never happen, because keys can only be numbers or 109 // strings and both can convert to string. 110 return cty.DynamicVal, path.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName()) 111 } 112 113 if conv != nil { 114 val, err = conv(val, path) 115 if err != nil { 116 return cty.NilVal, err 117 } 118 } 119 120 elems[keyStr.AsString()] = val 121 } 122 123 if len(elems) == 0 { 124 return cty.MapValEmpty(ety), nil 125 } 126 127 return cty.MapVal(elems), nil 128 } 129} 130 131// conversionTupleToSet returns a conversion that will take a value of the 132// given tuple type and return a set of the given element type. 133// 134// Will panic if the given tupleType isn't actually a tuple type. 135func conversionTupleToSet(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion { 136 tupleEtys := tupleType.TupleElementTypes() 137 138 if len(tupleEtys) == 0 { 139 // Empty tuple short-circuit 140 return func(val cty.Value, path cty.Path) (cty.Value, error) { 141 return cty.SetValEmpty(listEty), nil 142 } 143 } 144 145 if listEty == cty.DynamicPseudoType { 146 // This is a special case where the caller wants us to find 147 // a suitable single type that all elements can convert to, if 148 // possible. 149 listEty, _ = unify(tupleEtys, unsafe) 150 if listEty == cty.NilType { 151 return nil 152 } 153 } 154 155 elemConvs := make([]conversion, len(tupleEtys)) 156 for i, tupleEty := range tupleEtys { 157 if tupleEty.Equals(listEty) { 158 // no conversion required 159 continue 160 } 161 162 elemConvs[i] = getConversion(tupleEty, listEty, unsafe) 163 if elemConvs[i] == nil { 164 // If any of our element conversions are impossible, then the our 165 // whole conversion is impossible. 166 return nil 167 } 168 } 169 170 // If we fall out here then a conversion is possible, using the 171 // element conversions in elemConvs 172 return func(val cty.Value, path cty.Path) (cty.Value, error) { 173 elems := make([]cty.Value, 0, len(elemConvs)) 174 path = append(path, nil) 175 i := int64(0) 176 it := val.ElementIterator() 177 for it.Next() { 178 _, val := it.Element() 179 var err error 180 181 path[len(path)-1] = cty.IndexStep{ 182 Key: cty.NumberIntVal(i), 183 } 184 185 conv := elemConvs[i] 186 if conv != nil { 187 val, err = conv(val, path) 188 if err != nil { 189 return cty.NilVal, err 190 } 191 } 192 elems = append(elems, val) 193 194 i++ 195 } 196 197 return cty.SetVal(elems), nil 198 } 199} 200 201// conversionTupleToList returns a conversion that will take a value of the 202// given tuple type and return a list of the given element type. 203// 204// Will panic if the given tupleType isn't actually a tuple type. 205func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion { 206 tupleEtys := tupleType.TupleElementTypes() 207 208 if len(tupleEtys) == 0 { 209 // Empty tuple short-circuit 210 return func(val cty.Value, path cty.Path) (cty.Value, error) { 211 return cty.ListValEmpty(listEty), nil 212 } 213 } 214 215 if listEty == cty.DynamicPseudoType { 216 // This is a special case where the caller wants us to find 217 // a suitable single type that all elements can convert to, if 218 // possible. 219 listEty, _ = unify(tupleEtys, unsafe) 220 if listEty == cty.NilType { 221 return nil 222 } 223 } 224 225 elemConvs := make([]conversion, len(tupleEtys)) 226 for i, tupleEty := range tupleEtys { 227 if tupleEty.Equals(listEty) { 228 // no conversion required 229 continue 230 } 231 232 elemConvs[i] = getConversion(tupleEty, listEty, unsafe) 233 if elemConvs[i] == nil { 234 // If any of our element conversions are impossible, then the our 235 // whole conversion is impossible. 236 return nil 237 } 238 } 239 240 // If we fall out here then a conversion is possible, using the 241 // element conversions in elemConvs 242 return func(val cty.Value, path cty.Path) (cty.Value, error) { 243 elems := make([]cty.Value, 0, len(elemConvs)) 244 path = append(path, nil) 245 i := int64(0) 246 it := val.ElementIterator() 247 for it.Next() { 248 _, val := it.Element() 249 var err error 250 251 path[len(path)-1] = cty.IndexStep{ 252 Key: cty.NumberIntVal(i), 253 } 254 255 conv := elemConvs[i] 256 if conv != nil { 257 val, err = conv(val, path) 258 if err != nil { 259 return cty.NilVal, err 260 } 261 } 262 elems = append(elems, val) 263 264 i++ 265 } 266 267 return cty.ListVal(elems), nil 268 } 269} 270 271// conversionObjectToMap returns a conversion that will take a value of the 272// given object type and return a map of the given element type. 273// 274// Will panic if the given objectType isn't actually an object type. 275func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion { 276 objectAtys := objectType.AttributeTypes() 277 278 if len(objectAtys) == 0 { 279 // Empty object short-circuit 280 return func(val cty.Value, path cty.Path) (cty.Value, error) { 281 return cty.MapValEmpty(mapEty), nil 282 } 283 } 284 285 if mapEty == cty.DynamicPseudoType { 286 // This is a special case where the caller wants us to find 287 // a suitable single type that all elements can convert to, if 288 // possible. 289 objectAtysList := make([]cty.Type, 0, len(objectAtys)) 290 for _, aty := range objectAtys { 291 objectAtysList = append(objectAtysList, aty) 292 } 293 mapEty, _ = unify(objectAtysList, unsafe) 294 if mapEty == cty.NilType { 295 return nil 296 } 297 } 298 299 elemConvs := make(map[string]conversion, len(objectAtys)) 300 for name, objectAty := range objectAtys { 301 if objectAty.Equals(mapEty) { 302 // no conversion required 303 continue 304 } 305 306 elemConvs[name] = getConversion(objectAty, mapEty, unsafe) 307 if elemConvs[name] == nil { 308 // If any of our element conversions are impossible, then the our 309 // whole conversion is impossible. 310 return nil 311 } 312 } 313 314 // If we fall out here then a conversion is possible, using the 315 // element conversions in elemConvs 316 return func(val cty.Value, path cty.Path) (cty.Value, error) { 317 elems := make(map[string]cty.Value, len(elemConvs)) 318 path = append(path, nil) 319 it := val.ElementIterator() 320 for it.Next() { 321 name, val := it.Element() 322 var err error 323 324 path[len(path)-1] = cty.IndexStep{ 325 Key: name, 326 } 327 328 conv := elemConvs[name.AsString()] 329 if conv != nil { 330 val, err = conv(val, path) 331 if err != nil { 332 return cty.NilVal, err 333 } 334 } 335 elems[name.AsString()] = val 336 } 337 338 return cty.MapVal(elems), nil 339 } 340} 341