github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/field_reader_map.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package schema
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  )
    10  
    11  // MapFieldReader reads fields out of an untyped map[string]string to
    12  // the best of its ability.
    13  type MapFieldReader struct {
    14  	Map    MapReader
    15  	Schema map[string]*Schema
    16  }
    17  
    18  func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) {
    19  	k := strings.Join(address, ".")
    20  	schemaList := addrToSchema(address, r.Schema)
    21  	if len(schemaList) == 0 {
    22  		return FieldReadResult{}, nil
    23  	}
    24  
    25  	schema := schemaList[len(schemaList)-1]
    26  	switch schema.Type {
    27  	case TypeBool, TypeInt, TypeFloat, TypeString:
    28  		return r.readPrimitive(address, schema)
    29  	case TypeList:
    30  		return readListField(r, address, schema)
    31  	case TypeMap:
    32  		return r.readMap(k, schema)
    33  	case TypeSet:
    34  		return r.readSet(address, schema)
    35  	case typeObject:
    36  		return readObjectField(r, address, schema.Elem.(map[string]*Schema))
    37  	default:
    38  		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
    39  	}
    40  }
    41  
    42  func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) {
    43  	result := make(map[string]interface{})
    44  	resultSet := false
    45  
    46  	// If the name of the map field is directly in the map with an
    47  	// empty string, it means that the map is being deleted, so mark
    48  	// that is is set.
    49  	if v, ok := r.Map.Access(k); ok && v == "" {
    50  		resultSet = true
    51  	}
    52  
    53  	prefix := k + "."
    54  	r.Map.Range(func(k, v string) bool {
    55  		if strings.HasPrefix(k, prefix) {
    56  			resultSet = true
    57  
    58  			key := k[len(prefix):]
    59  			if key != "%" && key != "#" {
    60  				result[key] = v
    61  			}
    62  		}
    63  
    64  		return true
    65  	})
    66  
    67  	err := mapValuesToPrimitive(k, result, schema)
    68  	if err != nil {
    69  		return FieldReadResult{}, nil
    70  	}
    71  
    72  	var resultVal interface{}
    73  	if resultSet {
    74  		resultVal = result
    75  	}
    76  
    77  	return FieldReadResult{
    78  		Value:  resultVal,
    79  		Exists: resultSet,
    80  	}, nil
    81  }
    82  
    83  func (r *MapFieldReader) readPrimitive(
    84  	address []string, schema *Schema) (FieldReadResult, error) {
    85  	k := strings.Join(address, ".")
    86  	result, ok := r.Map.Access(k)
    87  	if !ok {
    88  		return FieldReadResult{}, nil
    89  	}
    90  
    91  	returnVal, err := stringToPrimitive(result, false, schema)
    92  	if err != nil {
    93  		return FieldReadResult{}, err
    94  	}
    95  
    96  	return FieldReadResult{
    97  		Value:  returnVal,
    98  		Exists: true,
    99  	}, nil
   100  }
   101  
   102  func (r *MapFieldReader) readSet(
   103  	address []string, schema *Schema) (FieldReadResult, error) {
   104  	// copy address to ensure we don't modify the argument
   105  	address = append([]string(nil), address...)
   106  
   107  	// Get the number of elements in the list
   108  	countRaw, err := r.readPrimitive(
   109  		append(address, "#"), &Schema{Type: TypeInt})
   110  	if err != nil {
   111  		return FieldReadResult{}, err
   112  	}
   113  	if !countRaw.Exists {
   114  		// No count, means we have no list
   115  		countRaw.Value = 0
   116  	}
   117  
   118  	// Create the set that will be our result
   119  	set := schema.ZeroValue().(*Set)
   120  
   121  	// If we have an empty list, then return an empty list
   122  	if countRaw.Computed || countRaw.Value.(int) == 0 {
   123  		return FieldReadResult{
   124  			Value:    set,
   125  			Exists:   countRaw.Exists,
   126  			Computed: countRaw.Computed,
   127  		}, nil
   128  	}
   129  
   130  	// Go through the map and find all the set items
   131  	prefix := strings.Join(address, ".") + "."
   132  	countExpected := countRaw.Value.(int)
   133  	countActual := make(map[string]struct{})
   134  	completed := r.Map.Range(func(k, _ string) bool {
   135  		if !strings.HasPrefix(k, prefix) {
   136  			return true
   137  		}
   138  		if strings.HasPrefix(k, prefix+"#") {
   139  			// Ignore the count field
   140  			return true
   141  		}
   142  
   143  		// Split the key, since it might be a sub-object like "idx.field"
   144  		parts := strings.Split(k[len(prefix):], ".")
   145  		idx := parts[0]
   146  
   147  		var raw FieldReadResult
   148  		raw, err = r.ReadField(append(address, idx))
   149  		if err != nil {
   150  			return false
   151  		}
   152  		if !raw.Exists {
   153  			// This shouldn't happen because we just verified it does exist
   154  			panic("missing field in set: " + k + "." + idx)
   155  		}
   156  
   157  		set.Add(raw.Value)
   158  
   159  		// Due to the way multimap readers work, if we've seen the number
   160  		// of fields we expect, then exit so that we don't read later values.
   161  		// For example: the "set" map might have "ports.#", "ports.0", and
   162  		// "ports.1", but the "state" map might have those plus "ports.2".
   163  		// We don't want "ports.2"
   164  		countActual[idx] = struct{}{}
   165  		if len(countActual) >= countExpected {
   166  			return false
   167  		}
   168  
   169  		return true
   170  	})
   171  	if !completed && err != nil {
   172  		return FieldReadResult{}, err
   173  	}
   174  
   175  	return FieldReadResult{
   176  		Value:  set,
   177  		Exists: true,
   178  	}, nil
   179  }
   180  
   181  // MapReader is an interface that is given to MapFieldReader for accessing
   182  // a "map". This can be used to have alternate implementations. For a basic
   183  // map[string]string, use BasicMapReader.
   184  type MapReader interface {
   185  	Access(string) (string, bool)
   186  	Range(func(string, string) bool) bool
   187  }
   188  
   189  // BasicMapReader implements MapReader for a single map.
   190  type BasicMapReader map[string]string
   191  
   192  func (r BasicMapReader) Access(k string) (string, bool) {
   193  	v, ok := r[k]
   194  	return v, ok
   195  }
   196  
   197  func (r BasicMapReader) Range(f func(string, string) bool) bool {
   198  	for k, v := range r {
   199  		if cont := f(k, v); !cont {
   200  			return false
   201  		}
   202  	}
   203  
   204  	return true
   205  }
   206  
   207  // MultiMapReader reads over multiple maps, preferring keys that are
   208  // founder earlier (lower number index) vs. later (higher number index)
   209  type MultiMapReader []map[string]string
   210  
   211  func (r MultiMapReader) Access(k string) (string, bool) {
   212  	for _, m := range r {
   213  		if v, ok := m[k]; ok {
   214  			return v, ok
   215  		}
   216  	}
   217  
   218  	return "", false
   219  }
   220  
   221  func (r MultiMapReader) Range(f func(string, string) bool) bool {
   222  	done := make(map[string]struct{})
   223  	for _, m := range r {
   224  		for k, v := range m {
   225  			if _, ok := done[k]; ok {
   226  				continue
   227  			}
   228  
   229  			if cont := f(k, v); !cont {
   230  				return false
   231  			}
   232  
   233  			done[k] = struct{}{}
   234  		}
   235  	}
   236  
   237  	return true
   238  }