216 lines
5.6 KiB
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
|
||
|
}
|