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