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