190 lines
4.5 KiB
Go
190 lines
4.5 KiB
Go
package hcl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclwrite"
|
|
|
|
"code.icb4dc0.de/buildr/common/hcl"
|
|
)
|
|
|
|
func (w *Writer[T]) marshalInto(val reflect.Value, block *hclwrite.Block) error {
|
|
if val.CanInterface() {
|
|
if marshaler, ok := val.Interface().(Marshaler); ok {
|
|
return marshaler.MarshalHCL(block)
|
|
}
|
|
}
|
|
//nolint:exhaustive // other cases will be added if necessary
|
|
switch val.Kind() {
|
|
case reflect.Pointer, reflect.Interface:
|
|
return w.marshalInto(val.Elem(), block)
|
|
case reflect.Struct:
|
|
return w.marshalStructInto(val, block)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *Writer[T]) marshalStructInto(val reflect.Value, block *hclwrite.Block) error {
|
|
if val.CanInterface() {
|
|
if marshaler, ok := val.Interface().(Marshaler); ok {
|
|
return marshaler.MarshalHCL(block)
|
|
}
|
|
}
|
|
valueType := val.Type()
|
|
for i := 0; i < val.NumField(); i++ {
|
|
fieldType := valueType.Field(i)
|
|
if err := w.marshalField(fieldType, val.Field(i), block); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//nolint:gocognit,gocyclo // hard to split
|
|
func (w *Writer[T]) marshalField(structField reflect.StructField, fieldVal reflect.Value, block *hclwrite.Block) error {
|
|
if !structField.IsExported() {
|
|
return nil
|
|
}
|
|
|
|
if w.SkipZeroValues && fieldVal.IsZero() {
|
|
return nil
|
|
}
|
|
|
|
marshalCfg, err := hcl.ExtractFieldMeta(structField, "hcl", "hcl_write")
|
|
if err != nil && !errors.Is(err, hcl.ErrNoHclTag) {
|
|
return err
|
|
}
|
|
|
|
if marshalCfg.Ignore {
|
|
return nil
|
|
}
|
|
|
|
handleStruct := func(val reflect.Value, t hcl.ElementType, name string) error {
|
|
var (
|
|
targetBlock *hclwrite.Block
|
|
needsAppending bool
|
|
)
|
|
|
|
//nolint:exhaustive // not necessary here
|
|
switch t {
|
|
case hcl.ElementTypeBlock:
|
|
targetBlock = hclwrite.NewBlock(name, discoverLabels(val.Type()))
|
|
needsAppending = true
|
|
case hcl.ElementTypeRemain:
|
|
targetBlock = block
|
|
default:
|
|
return fmt.Errorf("undefined block creation behavior for type %s", marshalCfg.Type)
|
|
}
|
|
|
|
if err := w.marshalStructInto(val, targetBlock); err != nil {
|
|
return err
|
|
}
|
|
|
|
if needsAppending {
|
|
block.Body().AppendBlock(targetBlock)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//nolint:exhaustive // not necessary here
|
|
switch fieldVal.Kind() {
|
|
case reflect.Map:
|
|
if isPrimitiveType(fieldVal.Type().Elem()) {
|
|
if v, err := MapToCtyVal(fieldVal, MappingCfg{}); err != nil {
|
|
return err
|
|
} else {
|
|
block.Body().SetAttributeValue(marshalCfg.Name, v)
|
|
}
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
if isPrimitiveType(fieldVal.Type().Elem()) || marshalCfg.Type == hcl.ElementTypeAttribute {
|
|
if v, err := MapToCtyVal(fieldVal, MappingCfg{}); err != nil {
|
|
return err
|
|
} else {
|
|
block.Body().SetAttributeValue(marshalCfg.Name, v)
|
|
}
|
|
} else {
|
|
for i := 0; i < fieldVal.Len(); i++ {
|
|
if err := handleStruct(fieldVal.Index(i), hcl.ElementTypeBlock, marshalCfg.Name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
case reflect.Pointer, reflect.Interface:
|
|
if fieldVal.IsNil() {
|
|
fieldVal = reflect.New(fieldVal.Type().Elem())
|
|
}
|
|
|
|
return w.marshalField(structField, fieldVal.Elem(), block)
|
|
case reflect.Struct:
|
|
if marshalCfg.Type != hcl.ElementTypeBlock && marshalCfg.Type != hcl.ElementTypeRemain {
|
|
return fmt.Errorf("field %s is a struct but not marked as block", structField.Name)
|
|
}
|
|
|
|
return handleStruct(fieldVal, marshalCfg.Type, marshalCfg.Name)
|
|
default:
|
|
if val, err := MapToCtyVal(fieldVal, MappingCfg{}); err == nil {
|
|
//nolint:exhaustive // not necessary here
|
|
switch marshalCfg.Type {
|
|
case hcl.ElementTypeAttribute:
|
|
block.Body().SetAttributeValue(marshalCfg.Name, val)
|
|
case hcl.ElementTypeLabel:
|
|
block.SetLabels(append(block.Labels(), val.AsString()))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func discoverLabels(t reflect.Type) []string {
|
|
labels := make([]string, 0)
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
structField := t.Field(1)
|
|
if !structField.IsExported() {
|
|
continue
|
|
}
|
|
|
|
marshalCfg, err := hcl.ExtractFieldMeta(structField, "hcl", "hcl_write")
|
|
if err != nil && !errors.Is(err, hcl.ErrNoHclTag) {
|
|
continue
|
|
}
|
|
|
|
if marshalCfg.Type == hcl.ElementTypeLabel {
|
|
labels = append(labels, marshalCfg.Name)
|
|
}
|
|
}
|
|
|
|
return labels
|
|
}
|
|
|
|
//nolint:exhaustive // not necessary here
|
|
func isPrimitiveType(t reflect.Type) bool {
|
|
switch t.Kind() {
|
|
case reflect.Pointer, reflect.Interface:
|
|
return isPrimitiveType(t.Elem())
|
|
case reflect.Bool,
|
|
reflect.Int,
|
|
reflect.Int8,
|
|
reflect.Int16,
|
|
reflect.Int32,
|
|
reflect.Int64,
|
|
reflect.Uint,
|
|
reflect.Uint8,
|
|
reflect.Uint16,
|
|
reflect.Uint32,
|
|
reflect.Uint64,
|
|
reflect.Float32,
|
|
reflect.Float64,
|
|
reflect.String:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|