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