From 5f063d321f13d1f5dd441c7b19c4c3ec1f6f3a85 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Dec 2014 06:15:04 -0500 Subject: [PATCH] helper/schema: FieldReader needs to return computed status --- helper/schema/field_reader.go | 234 +----------------- helper/schema/field_reader_map.go | 233 +++++++++++++++++ ...eader_test.go => field_reader_map_test.go} | 26 +- 3 files changed, 254 insertions(+), 239 deletions(-) create mode 100644 helper/schema/field_reader_map.go rename helper/schema/{field_reader_test.go => field_reader_map_test.go} (88%) diff --git a/helper/schema/field_reader.go b/helper/schema/field_reader.go index ba94280bd..dc99eaa70 100644 --- a/helper/schema/field_reader.go +++ b/helper/schema/field_reader.go @@ -1,240 +1,8 @@ package schema -import ( - "fmt" - "strconv" - "strings" -) - // FieldReaders are responsible for decoding fields out of data into // the proper typed representation. ResourceData uses this to query data // out of multiple sources: config, state, diffs, etc. type FieldReader interface { - ReadField([]string, *Schema) (interface{}, bool, error) -} - -// MapFieldReader reads fields out of an untyped map[string]string to -// the best of its ability. -type MapFieldReader struct { - Map map[string]string -} - -func (r *MapFieldReader) ReadField( - address []string, schema *Schema) (interface{}, bool, error) { - k := strings.Join(address, ".") - - switch schema.Type { - case TypeBool: - fallthrough - case TypeInt: - fallthrough - case TypeString: - return r.readPrimitive(k, schema) - case TypeList: - return r.readList(k, schema) - case TypeMap: - return r.readMap(k) - case TypeSet: - return r.readSet(k, schema) - case typeObject: - return r.readObject(k, schema.Elem.(map[string]*Schema)) - default: - panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) - } -} - -func (r *MapFieldReader) readObject( - k string, schema map[string]*Schema) (interface{}, bool, error) { - result := make(map[string]interface{}) - for field, schema := range schema { - v, ok, err := r.ReadField([]string{k, field}, schema) - if err != nil { - return nil, false, err - } - if !ok { - continue - } - - result[field] = v - } - - return result, true, nil -} - -func (r *MapFieldReader) readList( - k string, schema *Schema) (interface{}, bool, error) { - // Get the number of elements in the list - countRaw, countOk, err := r.readPrimitive( - k+".#", &Schema{Type: TypeInt}) - if err != nil { - return nil, false, err - } - if !countOk { - // No count, means we have no list - countRaw = 0 - } - - // If we have an empty list, then return an empty list - if countRaw.(int) == 0 { - return []interface{}{}, true, nil - } - - // Get the schema for the elements - var elemSchema *Schema - switch t := schema.Elem.(type) { - case *Resource: - elemSchema = &Schema{ - Type: typeObject, - Elem: t.Schema, - } - case *Schema: - elemSchema = t - } - - // Go through each count, and get the item value out of it - result := make([]interface{}, countRaw.(int)) - for i, _ := range result { - is := strconv.FormatInt(int64(i), 10) - raw, ok, err := r.ReadField([]string{k, is}, elemSchema) - if err != nil { - return nil, false, err - } - if !ok { - // This should never happen, because by the time the data - // gets to the FieldReaders, all the defaults should be set by - // Schema. - raw = nil - } - - result[i] = raw - } - - return result, true, nil -} - -func (r *MapFieldReader) readMap(k string) (interface{}, bool, error) { - result := make(map[string]interface{}) - resultSet := false - - prefix := k + "." - for k, v := range r.Map { - if !strings.HasPrefix(k, prefix) { - continue - } - - result[k[len(prefix):]] = v - resultSet = true - } - - var resultVal interface{} - if resultSet { - resultVal = result - } - - return resultVal, resultSet, nil -} - -func (r *MapFieldReader) readPrimitive( - k string, schema *Schema) (interface{}, bool, error) { - result, ok := r.Map[k] - if !ok { - return nil, false, nil - } - - var returnVal interface{} - switch schema.Type { - case TypeBool: - if result == "" { - returnVal = false - break - } - - v, err := strconv.ParseBool(result) - if err != nil { - return nil, false, err - } - - returnVal = v - case TypeInt: - if result == "" { - returnVal = 0 - break - } - - v, err := strconv.ParseInt(result, 0, 0) - if err != nil { - return nil, false, err - } - - returnVal = int(v) - case TypeString: - returnVal = result - default: - panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) - } - - return returnVal, true, nil -} - -func (r *MapFieldReader) readSet( - k string, schema *Schema) (interface{}, bool, error) { - // Get the number of elements in the list - countRaw, countOk, err := r.readPrimitive( - k+".#", &Schema{Type: TypeInt}) - if err != nil { - return nil, false, err - } - if !countOk { - // No count, means we have no list - countRaw = 0 - } - - // Create the set that will be our result - set := &Set{F: schema.Set} - - // If we have an empty list, then return an empty list - if countRaw.(int) == 0 { - return set, true, nil - } - - // Get the schema for the elements - var elemSchema *Schema - switch t := schema.Elem.(type) { - case *Resource: - elemSchema = &Schema{ - Type: typeObject, - Elem: t.Schema, - } - case *Schema: - elemSchema = t - } - - // Go through the map and find all the set items - prefix := k + "." - for k, _ := range r.Map { - if !strings.HasPrefix(k, prefix) { - continue - } - if strings.HasPrefix(k, prefix+"#") { - // Ignore the count field - continue - } - - // Split the key, since it might be a sub-object like "idx.field" - parts := strings.Split(k[len(prefix):], ".") - idx := parts[0] - - v, ok, err := r.ReadField([]string{prefix + idx}, elemSchema) - if err != nil { - return nil, false, err - } - if !ok { - // This shouldn't happen because we just verified it does exist - panic("missing field in set: " + k + "." + idx) - } - - set.Add(v) - } - - return set, true, nil + ReadField([]string, *Schema) (interface{}, bool, bool, error) } diff --git a/helper/schema/field_reader_map.go b/helper/schema/field_reader_map.go new file mode 100644 index 000000000..df9391a16 --- /dev/null +++ b/helper/schema/field_reader_map.go @@ -0,0 +1,233 @@ +package schema + +import ( + "fmt" + "strconv" + "strings" +) + +// MapFieldReader reads fields out of an untyped map[string]string to +// the best of its ability. +type MapFieldReader struct { + Map map[string]string +} + +func (r *MapFieldReader) ReadField( + address []string, schema *Schema) (interface{}, bool, bool, error) { + k := strings.Join(address, ".") + + switch schema.Type { + case TypeBool: + fallthrough + case TypeInt: + fallthrough + case TypeString: + return r.readPrimitive(k, schema) + case TypeList: + return r.readList(k, schema) + case TypeMap: + return r.readMap(k) + case TypeSet: + return r.readSet(k, schema) + case typeObject: + return r.readObject(k, schema.Elem.(map[string]*Schema)) + default: + panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) + } +} + +func (r *MapFieldReader) readObject( + k string, schema map[string]*Schema) (interface{}, bool, bool, error) { + result := make(map[string]interface{}) + for field, schema := range schema { + v, ok, _, err := r.ReadField([]string{k, field}, schema) + if err != nil { + return nil, false, false, err + } + if !ok { + continue + } + + result[field] = v + } + + return result, true, false, nil +} + +func (r *MapFieldReader) readList( + k string, schema *Schema) (interface{}, bool, bool, error) { + // Get the number of elements in the list + countRaw, countOk, countComputed, err := r.readPrimitive( + k+".#", &Schema{Type: TypeInt}) + if err != nil { + return nil, false, false, err + } + if !countOk { + // No count, means we have no list + countRaw = 0 + } + + // If we have an empty list, then return an empty list + if countComputed || countRaw.(int) == 0 { + return []interface{}{}, true, countComputed, nil + } + + // Get the schema for the elements + var elemSchema *Schema + switch t := schema.Elem.(type) { + case *Resource: + elemSchema = &Schema{ + Type: typeObject, + Elem: t.Schema, + } + case *Schema: + elemSchema = t + } + + // Go through each count, and get the item value out of it + result := make([]interface{}, countRaw.(int)) + for i, _ := range result { + is := strconv.FormatInt(int64(i), 10) + raw, ok, _, err := r.ReadField([]string{k, is}, elemSchema) + if err != nil { + return nil, false, false, err + } + if !ok { + // This should never happen, because by the time the data + // gets to the FieldReaders, all the defaults should be set by + // Schema. + raw = nil + } + + result[i] = raw + } + + return result, true, false, nil +} + +func (r *MapFieldReader) readMap(k string) (interface{}, bool, bool, error) { + result := make(map[string]interface{}) + resultSet := false + + prefix := k + "." + for k, v := range r.Map { + if !strings.HasPrefix(k, prefix) { + continue + } + + result[k[len(prefix):]] = v + resultSet = true + } + + var resultVal interface{} + if resultSet { + resultVal = result + } + + return resultVal, resultSet, false, nil +} + +func (r *MapFieldReader) readPrimitive( + k string, schema *Schema) (interface{}, bool, bool, error) { + result, ok := r.Map[k] + if !ok { + return nil, false, false, nil + } + + var returnVal interface{} + switch schema.Type { + case TypeBool: + if result == "" { + returnVal = false + break + } + + v, err := strconv.ParseBool(result) + if err != nil { + return nil, false, false, err + } + + returnVal = v + case TypeInt: + if result == "" { + returnVal = 0 + break + } + + v, err := strconv.ParseInt(result, 0, 0) + if err != nil { + return nil, false, false, err + } + + returnVal = int(v) + case TypeString: + returnVal = result + default: + panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) + } + + return returnVal, true, false, nil +} + +func (r *MapFieldReader) readSet( + k string, schema *Schema) (interface{}, bool, bool, error) { + // Get the number of elements in the list + countRaw, countOk, countComputed, err := r.readPrimitive( + k+".#", &Schema{Type: TypeInt}) + if err != nil { + return nil, false, false, err + } + if !countOk { + // No count, means we have no list + countRaw = 0 + } + + // Create the set that will be our result + set := &Set{F: schema.Set} + + // If we have an empty list, then return an empty list + if countComputed || countRaw.(int) == 0 { + return set, true, countComputed, nil + } + + // Get the schema for the elements + var elemSchema *Schema + switch t := schema.Elem.(type) { + case *Resource: + elemSchema = &Schema{ + Type: typeObject, + Elem: t.Schema, + } + case *Schema: + elemSchema = t + } + + // Go through the map and find all the set items + prefix := k + "." + for k, _ := range r.Map { + if !strings.HasPrefix(k, prefix) { + continue + } + if strings.HasPrefix(k, prefix+"#") { + // Ignore the count field + continue + } + + // Split the key, since it might be a sub-object like "idx.field" + parts := strings.Split(k[len(prefix):], ".") + idx := parts[0] + + v, ok, _, err := r.ReadField([]string{prefix + idx}, elemSchema) + if err != nil { + return nil, false, false, err + } + if !ok { + // This shouldn't happen because we just verified it does exist + panic("missing field in set: " + k + "." + idx) + } + + set.Add(v) + } + + return set, true, false, nil +} diff --git a/helper/schema/field_reader_test.go b/helper/schema/field_reader_map_test.go similarity index 88% rename from helper/schema/field_reader_test.go rename to helper/schema/field_reader_map_test.go index 642d7e10e..fba89a158 100644 --- a/helper/schema/field_reader_test.go +++ b/helper/schema/field_reader_map_test.go @@ -40,11 +40,12 @@ func TestMapFieldReader(t *testing.T) { } cases := map[string]struct { - Addr []string - Schema *Schema - Out interface{} - OutOk bool - OutErr bool + Addr []string + Schema *Schema + Out interface{} + OutOk bool + OutComputed bool + OutErr bool }{ "noexist": { []string{"boolNOPE"}, @@ -52,6 +53,7 @@ func TestMapFieldReader(t *testing.T) { nil, false, false, + false, }, "bool": { @@ -60,6 +62,7 @@ func TestMapFieldReader(t *testing.T) { true, true, false, + false, }, "int": { @@ -68,6 +71,7 @@ func TestMapFieldReader(t *testing.T) { 42, true, false, + false, }, "string": { @@ -76,6 +80,7 @@ func TestMapFieldReader(t *testing.T) { "string", true, false, + false, }, "list": { @@ -90,6 +95,7 @@ func TestMapFieldReader(t *testing.T) { }, true, false, + false, }, "listInt": { @@ -104,6 +110,7 @@ func TestMapFieldReader(t *testing.T) { }, true, false, + false, }, "map": { @@ -115,6 +122,7 @@ func TestMapFieldReader(t *testing.T) { }, true, false, + false, }, "mapelem": { @@ -123,6 +131,7 @@ func TestMapFieldReader(t *testing.T) { "bar", true, false, + false, }, "set": { @@ -137,6 +146,7 @@ func TestMapFieldReader(t *testing.T) { []interface{}{10, 50}, true, false, + false, }, "setDeep": { @@ -165,14 +175,18 @@ func TestMapFieldReader(t *testing.T) { }, true, false, + false, }, } for name, tc := range cases { - out, outOk, outErr := r.ReadField(tc.Addr, tc.Schema) + out, outOk, outComputed, outErr := r.ReadField(tc.Addr, tc.Schema) if (outErr != nil) != tc.OutErr { t.Fatalf("%s: err: %s", name, outErr) } + if outComputed != tc.OutComputed { + t.Fatalf("%s: err: %#v", name, outComputed) + } if s, ok := out.(*Set); ok { // If it is a set, convert to a list so its more easily checked.