github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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 result := s.Type.Zero() 42 43 // The zero value of a set is nil, but we want it 44 // to actually be an empty set object... 45 if set, ok := result.(*Set); ok && set.F == nil { 46 set.F = s.Set 47 } 48 49 return result 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: 82 fallthrough 83 case TypeInt: 84 fallthrough 85 case TypeFloat: 86 fallthrough 87 case TypeString: 88 if len(addr) > 0 { 89 return nil 90 } 91 case TypeList: 92 fallthrough 93 case TypeSet: 94 switch v := current.Elem.(type) { 95 case *Resource: 96 current = &Schema{ 97 Type: typeObject, 98 Elem: v.Schema, 99 } 100 case *Schema: 101 current = v 102 default: 103 return nil 104 } 105 106 // If we only have one more thing and the the next thing 107 // is a #, then we're accessing the index which is always 108 // an int. 109 if len(addr) > 0 && addr[0] == "#" { 110 current = &Schema{Type: TypeInt} 111 break 112 } 113 case TypeMap: 114 if len(addr) > 0 { 115 current = &Schema{Type: TypeString} 116 } 117 case typeObject: 118 // If we're already in the object, then we want to handle Sets 119 // and Lists specially. Basically, their next key is the lookup 120 // key (the set value or the list element). For these scenarios, 121 // we just want to skip it and move to the next element if there 122 // is one. 123 if len(result) > 0 { 124 lastType := result[len(result)-2].Type 125 if lastType == TypeSet || lastType == TypeList { 126 if len(addr) == 0 { 127 break 128 } 129 130 k = addr[0] 131 addr = addr[1:] 132 } 133 } 134 135 m := current.Elem.(map[string]*Schema) 136 val, ok := m[k] 137 if !ok { 138 return nil 139 } 140 141 current = val 142 goto REPEAT 143 } 144 } 145 146 return result 147 } 148 149 // readListField is a generic method for reading a list field out of a 150 // a FieldReader. It does this based on the assumption that there is a key 151 // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. 152 // after that point. 153 func readListField( 154 r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { 155 addrPadded := make([]string, len(addr)+1) 156 copy(addrPadded, addr) 157 addrPadded[len(addrPadded)-1] = "#" 158 159 // Get the number of elements in the list 160 countResult, err := r.ReadField(addrPadded) 161 if err != nil { 162 return FieldReadResult{}, err 163 } 164 if !countResult.Exists { 165 // No count, means we have no list 166 countResult.Value = 0 167 } 168 169 // If we have an empty list, then return an empty list 170 if countResult.Computed || countResult.Value.(int) == 0 { 171 return FieldReadResult{ 172 Value: []interface{}{}, 173 Exists: countResult.Exists, 174 Computed: countResult.Computed, 175 }, nil 176 } 177 178 // Go through each count, and get the item value out of it 179 result := make([]interface{}, countResult.Value.(int)) 180 for i, _ := range result { 181 is := strconv.FormatInt(int64(i), 10) 182 addrPadded[len(addrPadded)-1] = is 183 rawResult, err := r.ReadField(addrPadded) 184 if err != nil { 185 return FieldReadResult{}, err 186 } 187 if !rawResult.Exists { 188 // This should never happen, because by the time the data 189 // gets to the FieldReaders, all the defaults should be set by 190 // Schema. 191 rawResult.Value = nil 192 } 193 194 result[i] = rawResult.Value 195 } 196 197 return FieldReadResult{ 198 Value: result, 199 Exists: true, 200 }, nil 201 } 202 203 // readObjectField is a generic method for reading objects out of FieldReaders 204 // based on the assumption that building an address of []string{k, FIELD} 205 // will result in the proper field data. 206 func readObjectField( 207 r FieldReader, 208 addr []string, 209 schema map[string]*Schema) (FieldReadResult, error) { 210 result := make(map[string]interface{}) 211 exists := false 212 for field, s := range schema { 213 addrRead := make([]string, len(addr), len(addr)+1) 214 copy(addrRead, addr) 215 addrRead = append(addrRead, field) 216 rawResult, err := r.ReadField(addrRead) 217 if err != nil { 218 return FieldReadResult{}, err 219 } 220 if rawResult.Exists { 221 exists = true 222 } 223 224 result[field] = rawResult.ValueOrZero(s) 225 } 226 227 return FieldReadResult{ 228 Value: result, 229 Exists: exists, 230 }, nil 231 } 232 233 func stringToPrimitive( 234 value string, computed bool, schema *Schema) (interface{}, error) { 235 var returnVal interface{} 236 switch schema.Type { 237 case TypeBool: 238 if value == "" { 239 returnVal = false 240 break 241 } 242 243 v, err := strconv.ParseBool(value) 244 if err != nil { 245 return nil, err 246 } 247 248 returnVal = v 249 case TypeFloat: 250 if value == "" { 251 returnVal = 0.0 252 break 253 } 254 if computed { 255 break 256 } 257 258 v, err := strconv.ParseFloat(value, 64) 259 if err != nil { 260 return nil, err 261 } 262 263 returnVal = v 264 case TypeInt: 265 if value == "" { 266 returnVal = 0 267 break 268 } 269 if computed { 270 break 271 } 272 273 v, err := strconv.ParseInt(value, 0, 0) 274 if err != nil { 275 return nil, err 276 } 277 278 returnVal = int(v) 279 case TypeString: 280 returnVal = value 281 default: 282 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 283 } 284 285 return returnVal, nil 286 }