Files
steampipe/pkg/utils/cty_conversion.go

269 lines
5.7 KiB
Go

package utils
import (
"fmt"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
"github.com/zclconf/go-cty/cty/json"
)
// CtyToJSON converts a cty value to it;s JSON representation
func CtyToJSON(val cty.Value) (string, error) {
if !val.IsWhollyKnown() {
return "", fmt.Errorf("cannot serialize unknown values")
}
if val.IsNull() {
return "{}", nil
}
buf, err := json.Marshal(val, val.Type())
if err != nil {
return "", err
}
return string(buf), nil
}
// CtyToPostgresString convert a cty value into a postgres representation of the value
func CtyToPostgresString(v cty.Value) (valStr string, err error) {
ty := v.Type()
switch {
case ty.IsTupleType(), ty.IsListType():
{
var array []string
if array, err = ctyTupleToArrayOfPgStrings(v); err == nil {
valStr = fmt.Sprintf("array[%s]", strings.Join(array, ","))
}
return
}
}
switch ty {
case cty.Bool:
var target bool
if err = gocty.FromCtyValue(v, &target); err == nil {
valStr = fmt.Sprintf("%v", target)
}
case cty.Number:
var target int
if err = gocty.FromCtyValue(v, &target); err == nil {
valStr = fmt.Sprintf("%d", target)
return
} else {
var targetf float64
if err = gocty.FromCtyValue(v, &targetf); err == nil {
valStr = fmt.Sprintf("%d", target)
}
}
case cty.String:
var target string
if err := gocty.FromCtyValue(v, &target); err == nil {
valStr = fmt.Sprintf("'%s'", target)
}
default:
var json string
// wrap as postgres string
if json, err = CtyToJSON(v); err == nil {
valStr = fmt.Sprintf("'%s'::jsonb", json)
}
}
return valStr, err
}
// CtyToString convert a cty value into a string representation of the value
func CtyToString(v cty.Value) (valStr string, err error) {
if v.IsNull() || !v.IsWhollyKnown() {
return "", nil
}
ty := v.Type()
switch {
case ty.IsTupleType(), ty.IsListType():
{
var array []string
if array, err = ctyTupleToArrayOfPgStrings(v); err == nil {
valStr = fmt.Sprintf("[%s]", strings.Join(array, ","))
}
return
}
}
switch ty {
case cty.Bool:
var target bool
if err = gocty.FromCtyValue(v, &target); err == nil {
valStr = fmt.Sprintf("%v", target)
}
case cty.Number:
var target int
if err = gocty.FromCtyValue(v, &target); err == nil {
valStr = fmt.Sprintf("%d", target)
} else {
var targetf float64
if err = gocty.FromCtyValue(v, &targetf); err == nil {
valStr = fmt.Sprintf("%d", target)
}
}
case cty.String:
var target string
if err := gocty.FromCtyValue(v, &target); err == nil {
valStr = target
}
default:
var json string
// wrap as postgres string
if json, err = CtyToJSON(v); err == nil {
valStr = json
}
}
return valStr, err
}
func CtyToGo(v cty.Value) (val interface{}, err error) {
if v.IsNull() {
return nil, nil
}
ty := v.Type()
switch {
case ty.IsTupleType(), ty.IsListType():
{
var array []string
if array, err = ctyTupleToArrayOfStrings(v); err == nil {
val = array
}
return
}
}
switch ty {
case cty.Bool:
var target bool
if err = gocty.FromCtyValue(v, &target); err == nil {
val = target
}
case cty.Number:
var target int
if err = gocty.FromCtyValue(v, &target); err == nil {
val = target
} else {
var targetf float64
if err = gocty.FromCtyValue(v, &targetf); err == nil {
val = targetf
}
}
case cty.String:
var target string
if err := gocty.FromCtyValue(v, &target); err == nil {
val = target
}
default:
var json string
// wrap as postgres string
if json, err = CtyToJSON(v); err == nil {
val = json
}
}
return
}
// CtyTypeToHclType converts a cty type to a hcl type
// accept multiple types and use the first non null and non dynamic one
func CtyTypeToHclType(types ...cty.Type) string {
// find which if any of the types are non nil and not dynamic
t := getKnownType(types)
if t == cty.NilType {
return ""
}
friendlyName := t.FriendlyName()
// func to convert from ctyt aggregate syntax to hcl
convertAggregate := func(prefix string) (string, bool) {
if strings.HasPrefix(friendlyName, prefix) {
return fmt.Sprintf("%s(%s)", strings.TrimSuffix(prefix, " of "), strings.TrimPrefix(friendlyName, prefix)), true
}
return "", false
}
if convertedName, isList := convertAggregate("list of "); isList {
return convertedName
}
if convertedName, isMap := convertAggregate("map of "); isMap {
return convertedName
}
if convertedName, isSet := convertAggregate("set of "); isSet {
return convertedName
}
if friendlyName == "tuple" {
elementTypes := t.TupleElementTypes()
if len(elementTypes) == 0 {
// we cannot determine the eleemnt type
return "list"
}
// if there are element types, use the first one (assume homogeneous)
underlyingType := elementTypes[0]
return fmt.Sprintf("list(%s)", CtyTypeToHclType(underlyingType))
}
if friendlyName == "dynamic" {
return ""
}
return friendlyName
}
// from a list oif cty typoes, return the first which is non nil and not dynamic
func getKnownType(types []cty.Type) cty.Type {
for _, t := range types {
if t != cty.NilType && !t.HasDynamicTypes() {
return t
}
}
return cty.NilType
}
func ctyTupleToArrayOfPgStrings(val cty.Value) ([]string, error) {
var res []string
it := val.ElementIterator()
for it.Next() {
_, v := it.Element()
// decode the value into a postgres compatible
valStr, err := CtyToPostgresString(v)
if err != nil {
return nil, err
}
res = append(res, valStr)
}
return res, nil
}
func ctyTupleToArrayOfStrings(val cty.Value) ([]string, error) {
var res []string
it := val.ElementIterator()
for it.Next() {
_, v := it.Element()
var valStr string
if err := gocty.FromCtyValue(v, &valStr); err != nil {
return nil, err
}
res = append(res, valStr)
}
return res, nil
}