github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/field_reader.go (about)

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