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