github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_map.go (about)

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