github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/helper/schema/field_reader.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 ) 8 9 // FieldReaders are responsible for decoding fields out of data into 10 // the proper typed representation. ResourceData uses this to query data 11 // out of multiple sources: config, state, diffs, etc. 12 type FieldReader interface { 13 ReadField([]string) (FieldReadResult, error) 14 } 15 16 // FieldReadResult encapsulates all the resulting data from reading 17 // a field. 18 type FieldReadResult struct { 19 // Value is the actual read value. NegValue is the _negative_ value 20 // or the items that should be removed (if they existed). NegValue 21 // doesn't make sense for primitives but is important for any 22 // container types such as maps, sets, lists. 23 Value interface{} 24 ValueProcessed interface{} 25 26 // Exists is true if the field was found in the data. False means 27 // it wasn't found if there was no error. 28 Exists bool 29 30 // Computed is true if the field was found but the value 31 // is computed. 32 Computed bool 33 } 34 35 // ValueOrZero returns the value of this result or the zero value of the 36 // schema type, ensuring a consistent non-nil return value. 37 func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} { 38 if r.Value != nil { 39 return r.Value 40 } 41 42 return s.ZeroValue() 43 } 44 45 // SchemasForFlatmapPath tries its best to find a sequence of schemas that 46 // the given dot-delimited attribute path traverses through. 47 func SchemasForFlatmapPath(path string, schemaMap map[string]*Schema) []*Schema { 48 parts := strings.Split(path, ".") 49 return addrToSchema(parts, schemaMap) 50 } 51 52 // addrToSchema finds the final element schema for the given address 53 // and the given schema. It returns all the schemas that led to the final 54 // schema. These are in order of the address (out to in). 55 func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { 56 current := &Schema{ 57 Type: typeObject, 58 Elem: schemaMap, 59 } 60 61 // If we aren't given an address, then the user is requesting the 62 // full object, so we return the special value which is the full object. 63 if len(addr) == 0 { 64 return []*Schema{current} 65 } 66 67 result := make([]*Schema, 0, len(addr)) 68 for len(addr) > 0 { 69 k := addr[0] 70 addr = addr[1:] 71 72 REPEAT: 73 // We want to trim off the first "typeObject" since its not a 74 // real lookup that people do. i.e. []string{"foo"} in a structure 75 // isn't {typeObject, typeString}, its just a {typeString}. 76 if len(result) > 0 || current.Type != typeObject { 77 result = append(result, current) 78 } 79 80 switch t := current.Type; t { 81 case TypeBool, TypeInt, TypeFloat, TypeString: 82 if len(addr) > 0 { 83 return nil 84 } 85 case TypeList, TypeSet: 86 isIndex := len(addr) > 0 && addr[0] == "#" 87 88 switch v := current.Elem.(type) { 89 case *Resource: 90 current = &Schema{ 91 Type: typeObject, 92 Elem: v.Schema, 93 } 94 case *Schema: 95 current = v 96 case ValueType: 97 current = &Schema{Type: v} 98 default: 99 // we may not know the Elem type and are just looking for the 100 // index 101 if isIndex { 102 break 103 } 104 105 if len(addr) == 0 { 106 // we've processed the address, so return what we've 107 // collected 108 return result 109 } 110 111 if len(addr) == 1 { 112 if _, err := strconv.Atoi(addr[0]); err == nil { 113 // we're indexing a value without a schema. This can 114 // happen if the list is nested in another schema type. 115 // Default to a TypeString like we do with a map 116 current = &Schema{Type: TypeString} 117 break 118 } 119 } 120 121 return nil 122 } 123 124 // If we only have one more thing and the next thing 125 // is a #, then we're accessing the index which is always 126 // an int. 127 if isIndex { 128 current = &Schema{Type: TypeInt} 129 break 130 } 131 132 case TypeMap: 133 if len(addr) > 0 { 134 switch v := current.Elem.(type) { 135 case ValueType: 136 current = &Schema{Type: v} 137 case *Schema: 138 current, _ = current.Elem.(*Schema) 139 default: 140 // maps default to string values. This is all we can have 141 // if this is nested in another list or map. 142 current = &Schema{Type: TypeString} 143 } 144 } 145 case typeObject: 146 // If we're already in the object, then we want to handle Sets 147 // and Lists specially. Basically, their next key is the lookup 148 // key (the set value or the list element). For these scenarios, 149 // we just want to skip it and move to the next element if there 150 // is one. 151 if len(result) > 0 { 152 lastType := result[len(result)-2].Type 153 if lastType == TypeSet || lastType == TypeList { 154 if len(addr) == 0 { 155 break 156 } 157 158 k = addr[0] 159 addr = addr[1:] 160 } 161 } 162 163 m := current.Elem.(map[string]*Schema) 164 val, ok := m[k] 165 if !ok { 166 return nil 167 } 168 169 current = val 170 goto REPEAT 171 } 172 } 173 174 return result 175 } 176 177 // readListField is a generic method for reading a list field out of a 178 // a FieldReader. It does this based on the assumption that there is a key 179 // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. 180 // after that point. 181 func readListField( 182 r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { 183 addrPadded := make([]string, len(addr)+1) 184 copy(addrPadded, addr) 185 addrPadded[len(addrPadded)-1] = "#" 186 187 // Get the number of elements in the list 188 countResult, err := r.ReadField(addrPadded) 189 if err != nil { 190 return FieldReadResult{}, err 191 } 192 if !countResult.Exists { 193 // No count, means we have no list 194 countResult.Value = 0 195 } 196 197 // If we have an empty list, then return an empty list 198 if countResult.Computed || countResult.Value.(int) == 0 { 199 return FieldReadResult{ 200 Value: []interface{}{}, 201 Exists: countResult.Exists, 202 Computed: countResult.Computed, 203 }, nil 204 } 205 206 // Go through each count, and get the item value out of it 207 result := make([]interface{}, countResult.Value.(int)) 208 for i, _ := range result { 209 is := strconv.FormatInt(int64(i), 10) 210 addrPadded[len(addrPadded)-1] = is 211 rawResult, err := r.ReadField(addrPadded) 212 if err != nil { 213 return FieldReadResult{}, err 214 } 215 if !rawResult.Exists { 216 // This should never happen, because by the time the data 217 // gets to the FieldReaders, all the defaults should be set by 218 // Schema. 219 rawResult.Value = nil 220 } 221 222 result[i] = rawResult.Value 223 } 224 225 return FieldReadResult{ 226 Value: result, 227 Exists: true, 228 }, nil 229 } 230 231 // readObjectField is a generic method for reading objects out of FieldReaders 232 // based on the assumption that building an address of []string{k, FIELD} 233 // will result in the proper field data. 234 func readObjectField( 235 r FieldReader, 236 addr []string, 237 schema map[string]*Schema) (FieldReadResult, error) { 238 result := make(map[string]interface{}) 239 exists := false 240 for field, s := range schema { 241 addrRead := make([]string, len(addr), len(addr)+1) 242 copy(addrRead, addr) 243 addrRead = append(addrRead, field) 244 rawResult, err := r.ReadField(addrRead) 245 if err != nil { 246 return FieldReadResult{}, err 247 } 248 if rawResult.Exists { 249 exists = true 250 } 251 252 result[field] = rawResult.ValueOrZero(s) 253 } 254 255 return FieldReadResult{ 256 Value: result, 257 Exists: exists, 258 }, nil 259 } 260 261 // convert map values to the proper primitive type based on schema.Elem 262 func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error { 263 elemType, err := getValueType(k, schema) 264 if err != nil { 265 return err 266 } 267 268 switch elemType { 269 case TypeInt, TypeFloat, TypeBool: 270 for k, v := range m { 271 vs, ok := v.(string) 272 if !ok { 273 continue 274 } 275 276 v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) 277 if err != nil { 278 return err 279 } 280 281 m[k] = v 282 } 283 } 284 return nil 285 } 286 287 func stringToPrimitive( 288 value string, computed bool, schema *Schema) (interface{}, error) { 289 var returnVal interface{} 290 switch schema.Type { 291 case TypeBool: 292 if value == "" { 293 returnVal = false 294 break 295 } 296 if computed { 297 break 298 } 299 300 v, err := strconv.ParseBool(value) 301 if err != nil { 302 return nil, err 303 } 304 305 returnVal = v 306 case TypeFloat: 307 if value == "" { 308 returnVal = 0.0 309 break 310 } 311 if computed { 312 break 313 } 314 315 v, err := strconv.ParseFloat(value, 64) 316 if err != nil { 317 return nil, err 318 } 319 320 returnVal = v 321 case TypeInt: 322 if value == "" { 323 returnVal = 0 324 break 325 } 326 if computed { 327 break 328 } 329 330 v, err := strconv.ParseInt(value, 0, 0) 331 if err != nil { 332 return nil, err 333 } 334 335 returnVal = int(v) 336 case TypeString: 337 returnVal = value 338 default: 339 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 340 } 341 342 return returnVal, nil 343 }