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 }