opentofu/helper/schema/field_reader.go
Andrew Langhorn bb4f7415ca Rename "The The" so that they can play songs again
Other than the fact that "The the" doesn't really make any sense anywhere
that it's used in Terraform, they're a post-punk band from the UK.

Fixes "The The" so that they can get back to playing songs.
2015-05-06 22:53:11 +01:00

287 lines
6.7 KiB
Go

package schema
import (
"fmt"
"strconv"
)
// FieldReaders are responsible for decoding fields out of data into
// the proper typed representation. ResourceData uses this to query data
// out of multiple sources: config, state, diffs, etc.
type FieldReader interface {
ReadField([]string) (FieldReadResult, error)
}
// FieldReadResult encapsulates all the resulting data from reading
// a field.
type FieldReadResult struct {
// Value is the actual read value. NegValue is the _negative_ value
// or the items that should be removed (if they existed). NegValue
// doesn't make sense for primitives but is important for any
// container types such as maps, sets, lists.
Value interface{}
ValueProcessed interface{}
// Exists is true if the field was found in the data. False means
// it wasn't found if there was no error.
Exists bool
// Computed is true if the field was found but the value
// is computed.
Computed bool
}
// ValueOrZero returns the value of this result or the zero value of the
// schema type, ensuring a consistent non-nil return value.
func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
if r.Value != nil {
return r.Value
}
result := s.Type.Zero()
// The zero value of a set is nil, but we want it
// to actually be an empty set object...
if set, ok := result.(*Set); ok && set.F == nil {
set.F = s.Set
}
return result
}
// addrToSchema finds the final element schema for the given address
// and the given schema. It returns all the schemas that led to the final
// schema. These are in order of the address (out to in).
func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
current := &Schema{
Type: typeObject,
Elem: schemaMap,
}
// If we aren't given an address, then the user is requesting the
// full object, so we return the special value which is the full object.
if len(addr) == 0 {
return []*Schema{current}
}
result := make([]*Schema, 0, len(addr))
for len(addr) > 0 {
k := addr[0]
addr = addr[1:]
REPEAT:
// We want to trim off the first "typeObject" since its not a
// real lookup that people do. i.e. []string{"foo"} in a structure
// isn't {typeObject, typeString}, its just a {typeString}.
if len(result) > 0 || current.Type != typeObject {
result = append(result, current)
}
switch t := current.Type; t {
case TypeBool:
fallthrough
case TypeInt:
fallthrough
case TypeFloat:
fallthrough
case TypeString:
if len(addr) > 0 {
return nil
}
case TypeList:
fallthrough
case TypeSet:
switch v := current.Elem.(type) {
case *Resource:
current = &Schema{
Type: typeObject,
Elem: v.Schema,
}
case *Schema:
current = v
default:
return nil
}
// If we only have one more thing and the next thing
// is a #, then we're accessing the index which is always
// an int.
if len(addr) > 0 && addr[0] == "#" {
current = &Schema{Type: TypeInt}
break
}
case TypeMap:
if len(addr) > 0 {
current = &Schema{Type: TypeString}
}
case typeObject:
// If we're already in the object, then we want to handle Sets
// and Lists specially. Basically, their next key is the lookup
// key (the set value or the list element). For these scenarios,
// we just want to skip it and move to the next element if there
// is one.
if len(result) > 0 {
lastType := result[len(result)-2].Type
if lastType == TypeSet || lastType == TypeList {
if len(addr) == 0 {
break
}
k = addr[0]
addr = addr[1:]
}
}
m := current.Elem.(map[string]*Schema)
val, ok := m[k]
if !ok {
return nil
}
current = val
goto REPEAT
}
}
return result
}
// readListField is a generic method for reading a list field out of a
// a FieldReader. It does this based on the assumption that there is a key
// "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc.
// after that point.
func readListField(
r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) {
addrPadded := make([]string, len(addr)+1)
copy(addrPadded, addr)
addrPadded[len(addrPadded)-1] = "#"
// Get the number of elements in the list
countResult, err := r.ReadField(addrPadded)
if err != nil {
return FieldReadResult{}, err
}
if !countResult.Exists {
// No count, means we have no list
countResult.Value = 0
}
// If we have an empty list, then return an empty list
if countResult.Computed || countResult.Value.(int) == 0 {
return FieldReadResult{
Value: []interface{}{},
Exists: countResult.Exists,
Computed: countResult.Computed,
}, nil
}
// Go through each count, and get the item value out of it
result := make([]interface{}, countResult.Value.(int))
for i, _ := range result {
is := strconv.FormatInt(int64(i), 10)
addrPadded[len(addrPadded)-1] = is
rawResult, err := r.ReadField(addrPadded)
if err != nil {
return FieldReadResult{}, err
}
if !rawResult.Exists {
// This should never happen, because by the time the data
// gets to the FieldReaders, all the defaults should be set by
// Schema.
rawResult.Value = nil
}
result[i] = rawResult.Value
}
return FieldReadResult{
Value: result,
Exists: true,
}, nil
}
// readObjectField is a generic method for reading objects out of FieldReaders
// based on the assumption that building an address of []string{k, FIELD}
// will result in the proper field data.
func readObjectField(
r FieldReader,
addr []string,
schema map[string]*Schema) (FieldReadResult, error) {
result := make(map[string]interface{})
exists := false
for field, s := range schema {
addrRead := make([]string, len(addr), len(addr)+1)
copy(addrRead, addr)
addrRead = append(addrRead, field)
rawResult, err := r.ReadField(addrRead)
if err != nil {
return FieldReadResult{}, err
}
if rawResult.Exists {
exists = true
}
result[field] = rawResult.ValueOrZero(s)
}
return FieldReadResult{
Value: result,
Exists: exists,
}, nil
}
func stringToPrimitive(
value string, computed bool, schema *Schema) (interface{}, error) {
var returnVal interface{}
switch schema.Type {
case TypeBool:
if value == "" {
returnVal = false
break
}
v, err := strconv.ParseBool(value)
if err != nil {
return nil, err
}
returnVal = v
case TypeFloat:
if value == "" {
returnVal = 0.0
break
}
if computed {
break
}
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, err
}
returnVal = v
case TypeInt:
if value == "" {
returnVal = 0
break
}
if computed {
break
}
v, err := strconv.ParseInt(value, 0, 0)
if err != nil {
return nil, err
}
returnVal = int(v)
case TypeString:
returnVal = value
default:
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
}
return returnVal, nil
}