terraform/vendor/github.com/zclconf/go-cty-yaml/yaml.go

216 lines
5.6 KiB
Go

// Package yaml can marshal and unmarshal cty values in YAML format.
package yaml
import (
"errors"
"fmt"
"reflect"
"strings"
"sync"
"github.com/zclconf/go-cty/cty"
)
// Unmarshal reads the document found within the given source buffer
// and attempts to convert it into a value conforming to the given type
// constraint.
//
// This is an alias for Unmarshal on the predefined Converter in "Standard".
//
// An error is returned if the given source contains any YAML document
// delimiters.
func Unmarshal(src []byte, ty cty.Type) (cty.Value, error) {
return Standard.Unmarshal(src, ty)
}
// Marshal serializes the given value into a YAML document, using a fixed
// mapping from cty types to YAML constructs.
//
// This is an alias for Marshal on the predefined Converter in "Standard".
//
// Note that unlike the function of the same name in the cty JSON package,
// this does not take a type constraint and therefore the YAML serialization
// cannot preserve late-bound type information in the serialization to be
// recovered from Unmarshal. Instead, any cty.DynamicPseudoType in the type
// constraint given to Unmarshal will be decoded as if the corresponding portion
// of the input were processed with ImpliedType to find a target type.
func Marshal(v cty.Value) ([]byte, error) {
return Standard.Marshal(v)
}
// ImpliedType analyzes the given source code and returns a suitable type that
// it could be decoded into.
//
// For a converter that is using standard YAML rather than cty-specific custom
// tags, only a subset of cty types can be produced: strings, numbers, bools,
// tuple types, and object types.
//
// This is an alias for ImpliedType on the predefined Converter in "Standard".
func ImpliedType(src []byte) (cty.Type, error) {
return Standard.ImpliedType(src)
}
func handleErr(err *error) {
if v := recover(); v != nil {
if e, ok := v.(yamlError); ok {
*err = e.err
} else {
panic(v)
}
}
}
type yamlError struct {
err error
}
func fail(err error) {
panic(yamlError{err})
}
func failf(format string, args ...interface{}) {
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
}
// --------------------------------------------------------------------------
// Maintain a mapping of keys to structure field indexes
// The code in this section was copied from mgo/bson.
// structInfo holds details for the serialization of fields of
// a given struct.
type structInfo struct {
FieldsMap map[string]fieldInfo
FieldsList []fieldInfo
// InlineMap is the number of the field in the struct that
// contains an ,inline map, or -1 if there's none.
InlineMap int
}
type fieldInfo struct {
Key string
Num int
OmitEmpty bool
Flow bool
// Id holds the unique field identifier, so we can cheaply
// check for field duplicates without maintaining an extra map.
Id int
// Inline holds the field index if the field is part of an inlined struct.
Inline []int
}
var structMap = make(map[reflect.Type]*structInfo)
var fieldMapMutex sync.RWMutex
func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldMapMutex.RLock()
sinfo, found := structMap[st]
fieldMapMutex.RUnlock()
if found {
return sinfo, nil
}
n := st.NumField()
fieldsMap := make(map[string]fieldInfo)
fieldsList := make([]fieldInfo, 0, n)
inlineMap := -1
for i := 0; i != n; i++ {
field := st.Field(i)
if field.PkgPath != "" && !field.Anonymous {
continue // Private field
}
info := fieldInfo{Num: i}
tag := field.Tag.Get("yaml")
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
tag = string(field.Tag)
}
if tag == "-" {
continue
}
inline := false
fields := strings.Split(tag, ",")
if len(fields) > 1 {
for _, flag := range fields[1:] {
switch flag {
case "omitempty":
info.OmitEmpty = true
case "flow":
info.Flow = true
case "inline":
inline = true
default:
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
}
}
tag = fields[0]
}
if inline {
switch field.Type.Kind() {
case reflect.Map:
if inlineMap >= 0 {
return nil, errors.New("Multiple ,inline maps in struct " + st.String())
}
if field.Type.Key() != reflect.TypeOf("") {
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
}
inlineMap = info.Num
case reflect.Struct:
sinfo, err := getStructInfo(field.Type)
if err != nil {
return nil, err
}
for _, finfo := range sinfo.FieldsList {
if _, found := fieldsMap[finfo.Key]; found {
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
if finfo.Inline == nil {
finfo.Inline = []int{i, finfo.Num}
} else {
finfo.Inline = append([]int{i}, finfo.Inline...)
}
finfo.Id = len(fieldsList)
fieldsMap[finfo.Key] = finfo
fieldsList = append(fieldsList, finfo)
}
default:
//return nil, errors.New("Option ,inline needs a struct value or map field")
return nil, errors.New("Option ,inline needs a struct value field")
}
continue
}
if tag != "" {
info.Key = tag
} else {
info.Key = strings.ToLower(field.Name)
}
if _, found = fieldsMap[info.Key]; found {
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
info.Id = len(fieldsList)
fieldsList = append(fieldsList, info)
fieldsMap[info.Key] = info
}
sinfo = &structInfo{
FieldsMap: fieldsMap,
FieldsList: fieldsList,
InlineMap: inlineMap,
}
fieldMapMutex.Lock()
structMap[st] = sinfo
fieldMapMutex.Unlock()
return sinfo, nil
}