168 lines
4 KiB
Go
168 lines
4 KiB
Go
package hcl
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type MappingCfg struct {
|
|
MapAsObject bool
|
|
StructAsMap bool
|
|
}
|
|
|
|
//nolint:gocyclo // expected to be complex as entrypoint
|
|
func MapToCtyVal(val any, cfg MappingCfg) (cty.Value, error) {
|
|
if val == nil {
|
|
return cty.Value{}, nil
|
|
}
|
|
|
|
var (
|
|
t reflect.Type
|
|
value reflect.Value
|
|
)
|
|
|
|
switch unwrapped := val.(type) {
|
|
case cty.Value:
|
|
return unwrapped, nil
|
|
case reflect.Value:
|
|
value = unwrapped
|
|
t = value.Type()
|
|
default:
|
|
t = reflect.TypeOf(val)
|
|
value = reflect.ValueOf(val)
|
|
}
|
|
|
|
if t.AssignableTo(reflect.TypeOf((*hclsyntax.Body)(nil))) {
|
|
return cty.EmptyObjectVal, nil
|
|
}
|
|
|
|
//nolint:exhaustive // other cases will be added if necessary
|
|
switch t.Kind() {
|
|
case reflect.Bool:
|
|
return cty.BoolVal(value.Bool()), nil
|
|
case reflect.String:
|
|
return cty.StringVal(value.String()), nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return cty.NumberIntVal(value.Int()), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return cty.NumberUIntVal(value.Uint()), nil
|
|
case reflect.Float32, reflect.Float64:
|
|
return cty.NumberFloatVal(value.Float()), nil
|
|
case reflect.Pointer:
|
|
return MapToCtyVal(reflect.ValueOf(val).Elem().Interface(), cfg)
|
|
case reflect.Map:
|
|
return mapMap(t, value, cfg)
|
|
case reflect.Slice, reflect.Array:
|
|
length := value.Len()
|
|
|
|
if length == 0 {
|
|
return cty.ListValEmpty(mapType(t.Elem())), nil
|
|
}
|
|
|
|
out := make([]cty.Value, 0, length)
|
|
|
|
for i := 0; i < length; i++ {
|
|
if mapped, err := MapToCtyVal(value.Index(i).Interface(), cfg); err != nil {
|
|
return cty.Value{}, err
|
|
} else {
|
|
out = append(out, mapped)
|
|
}
|
|
}
|
|
|
|
return cty.ListVal(out), nil
|
|
case reflect.Struct:
|
|
return mapStruct(t, value, cfg)
|
|
default:
|
|
return cty.NilVal, fmt.Errorf("unmapped type %s", t.Kind())
|
|
}
|
|
}
|
|
|
|
func mapStruct(t reflect.Type, val reflect.Value, cfg MappingCfg) (cty.Value, error) {
|
|
numField := t.NumField()
|
|
out := make(map[string]cty.Value, numField)
|
|
|
|
for i := 0; i < numField; i++ {
|
|
field := t.Field(i)
|
|
if field.IsExported() && !isNil(val.Field(i)) {
|
|
if mapped, err := MapToCtyVal(val.Field(i).Interface(), cfg); err != nil {
|
|
return cty.Value{}, err
|
|
} else if field.Anonymous && mapped.CanIterateElements() {
|
|
for key, val := range mapped.AsValueMap() {
|
|
out[key] = val
|
|
}
|
|
} else {
|
|
keyName := strings.ToLower(field.Name)
|
|
if hclTag := field.Tag.Get("hcl"); hclTag != "" {
|
|
split := strings.Split(hclTag, ",")
|
|
if len(split) > 0 && split[0] != "" {
|
|
keyName = split[0]
|
|
}
|
|
}
|
|
out[keyName] = mapped
|
|
}
|
|
}
|
|
}
|
|
|
|
if cfg.StructAsMap {
|
|
return cty.MapVal(out), nil
|
|
}
|
|
|
|
return cty.ObjectVal(out), nil
|
|
}
|
|
|
|
func mapMap(t reflect.Type, val reflect.Value, cfg MappingCfg) (cty.Value, error) {
|
|
if val.Len() == 0 {
|
|
return cty.MapValEmpty(mapType(t.Elem())), nil
|
|
}
|
|
if t.Key().Kind() != reflect.String {
|
|
return cty.Value{}, fmt.Errorf("map key type %s is not supported", t.Key().Name())
|
|
}
|
|
|
|
out := make(map[string]cty.Value)
|
|
|
|
iter := val.MapRange()
|
|
for iter.Next() {
|
|
if mapped, err := MapToCtyVal(iter.Value().Interface(), cfg); err != nil {
|
|
return cty.Value{}, err
|
|
} else {
|
|
out[iter.Key().String()] = mapped
|
|
}
|
|
}
|
|
|
|
if cfg.MapAsObject {
|
|
return cty.ObjectVal(out), nil
|
|
}
|
|
|
|
return cty.MapVal(out), nil
|
|
}
|
|
|
|
func isNil(val reflect.Value) bool {
|
|
//nolint:exhaustive // other cases will be added if necessary
|
|
switch val.Kind() {
|
|
case reflect.Pointer, reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice:
|
|
return val.IsNil()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func mapType(t reflect.Type) cty.Type {
|
|
//nolint:exhaustive // other cases will be added if necessary
|
|
switch t.Kind() {
|
|
case reflect.Bool:
|
|
return cty.Bool
|
|
case reflect.String:
|
|
return cty.String
|
|
case reflect.Float32, reflect.Float64,
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return cty.Number
|
|
default:
|
|
return cty.Type{}
|
|
}
|
|
}
|