mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
b5de50c0a2
There were a couple spots where argument slices weren't being copied before `append` was called, which could possibly modify the caller's slice data.
236 lines
5.4 KiB
Go
236 lines
5.4 KiB
Go
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// MapFieldReader reads fields out of an untyped map[string]string to
|
|
// the best of its ability.
|
|
type MapFieldReader struct {
|
|
Map MapReader
|
|
Schema map[string]*Schema
|
|
}
|
|
|
|
func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
|
k := strings.Join(address, ".")
|
|
schemaList := addrToSchema(address, r.Schema)
|
|
if len(schemaList) == 0 {
|
|
return FieldReadResult{}, nil
|
|
}
|
|
|
|
schema := schemaList[len(schemaList)-1]
|
|
switch schema.Type {
|
|
case TypeBool, TypeInt, TypeFloat, TypeString:
|
|
return r.readPrimitive(address, schema)
|
|
case TypeList:
|
|
return readListField(r, address, schema)
|
|
case TypeMap:
|
|
return r.readMap(k, schema)
|
|
case TypeSet:
|
|
return r.readSet(address, schema)
|
|
case typeObject:
|
|
return readObjectField(r, address, schema.Elem.(map[string]*Schema))
|
|
default:
|
|
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
|
}
|
|
}
|
|
|
|
func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) {
|
|
result := make(map[string]interface{})
|
|
resultSet := false
|
|
|
|
// If the name of the map field is directly in the map with an
|
|
// empty string, it means that the map is being deleted, so mark
|
|
// that is is set.
|
|
if v, ok := r.Map.Access(k); ok && v == "" {
|
|
resultSet = true
|
|
}
|
|
|
|
prefix := k + "."
|
|
r.Map.Range(func(k, v string) bool {
|
|
if strings.HasPrefix(k, prefix) {
|
|
resultSet = true
|
|
|
|
key := k[len(prefix):]
|
|
if key != "%" && key != "#" {
|
|
result[key] = v
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
err := mapValuesToPrimitive(k, result, schema)
|
|
if err != nil {
|
|
return FieldReadResult{}, nil
|
|
}
|
|
|
|
var resultVal interface{}
|
|
if resultSet {
|
|
resultVal = result
|
|
}
|
|
|
|
return FieldReadResult{
|
|
Value: resultVal,
|
|
Exists: resultSet,
|
|
}, nil
|
|
}
|
|
|
|
func (r *MapFieldReader) readPrimitive(
|
|
address []string, schema *Schema) (FieldReadResult, error) {
|
|
k := strings.Join(address, ".")
|
|
result, ok := r.Map.Access(k)
|
|
if !ok {
|
|
return FieldReadResult{}, nil
|
|
}
|
|
|
|
returnVal, err := stringToPrimitive(result, false, schema)
|
|
if err != nil {
|
|
return FieldReadResult{}, err
|
|
}
|
|
|
|
return FieldReadResult{
|
|
Value: returnVal,
|
|
Exists: true,
|
|
}, nil
|
|
}
|
|
|
|
func (r *MapFieldReader) readSet(
|
|
address []string, schema *Schema) (FieldReadResult, error) {
|
|
// copy address to ensure we don't modify the argument
|
|
address = append([]string(nil), address...)
|
|
|
|
// Get the number of elements in the list
|
|
countRaw, err := r.readPrimitive(
|
|
append(address, "#"), &Schema{Type: TypeInt})
|
|
if err != nil {
|
|
return FieldReadResult{}, err
|
|
}
|
|
if !countRaw.Exists {
|
|
// No count, means we have no list
|
|
countRaw.Value = 0
|
|
}
|
|
|
|
// Create the set that will be our result
|
|
set := schema.ZeroValue().(*Set)
|
|
|
|
// If we have an empty list, then return an empty list
|
|
if countRaw.Computed || countRaw.Value.(int) == 0 {
|
|
return FieldReadResult{
|
|
Value: set,
|
|
Exists: countRaw.Exists,
|
|
Computed: countRaw.Computed,
|
|
}, nil
|
|
}
|
|
|
|
// Go through the map and find all the set items
|
|
prefix := strings.Join(address, ".") + "."
|
|
countExpected := countRaw.Value.(int)
|
|
countActual := make(map[string]struct{})
|
|
completed := r.Map.Range(func(k, _ string) bool {
|
|
if !strings.HasPrefix(k, prefix) {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(k, prefix+"#") {
|
|
// Ignore the count field
|
|
return true
|
|
}
|
|
|
|
// Split the key, since it might be a sub-object like "idx.field"
|
|
parts := strings.Split(k[len(prefix):], ".")
|
|
idx := parts[0]
|
|
|
|
var raw FieldReadResult
|
|
raw, err = r.ReadField(append(address, idx))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if !raw.Exists {
|
|
// This shouldn't happen because we just verified it does exist
|
|
panic("missing field in set: " + k + "." + idx)
|
|
}
|
|
|
|
set.Add(raw.Value)
|
|
|
|
// Due to the way multimap readers work, if we've seen the number
|
|
// of fields we expect, then exit so that we don't read later values.
|
|
// For example: the "set" map might have "ports.#", "ports.0", and
|
|
// "ports.1", but the "state" map might have those plus "ports.2".
|
|
// We don't want "ports.2"
|
|
countActual[idx] = struct{}{}
|
|
if len(countActual) >= countExpected {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
if !completed && err != nil {
|
|
return FieldReadResult{}, err
|
|
}
|
|
|
|
return FieldReadResult{
|
|
Value: set,
|
|
Exists: true,
|
|
}, nil
|
|
}
|
|
|
|
// MapReader is an interface that is given to MapFieldReader for accessing
|
|
// a "map". This can be used to have alternate implementations. For a basic
|
|
// map[string]string, use BasicMapReader.
|
|
type MapReader interface {
|
|
Access(string) (string, bool)
|
|
Range(func(string, string) bool) bool
|
|
}
|
|
|
|
// BasicMapReader implements MapReader for a single map.
|
|
type BasicMapReader map[string]string
|
|
|
|
func (r BasicMapReader) Access(k string) (string, bool) {
|
|
v, ok := r[k]
|
|
return v, ok
|
|
}
|
|
|
|
func (r BasicMapReader) Range(f func(string, string) bool) bool {
|
|
for k, v := range r {
|
|
if cont := f(k, v); !cont {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MultiMapReader reads over multiple maps, preferring keys that are
|
|
// founder earlier (lower number index) vs. later (higher number index)
|
|
type MultiMapReader []map[string]string
|
|
|
|
func (r MultiMapReader) Access(k string) (string, bool) {
|
|
for _, m := range r {
|
|
if v, ok := m[k]; ok {
|
|
return v, ok
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
func (r MultiMapReader) Range(f func(string, string) bool) bool {
|
|
done := make(map[string]struct{})
|
|
for _, m := range r {
|
|
for k, v := range m {
|
|
if _, ok := done[k]; ok {
|
|
continue
|
|
}
|
|
|
|
if cont := f(k, v); !cont {
|
|
return false
|
|
}
|
|
|
|
done[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|