buildr/internal/hcl/marshal.go
Peter e60726ef9e
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
feat: implement new and man for plugin modules
- use extracted shared libraries
2023-08-23 22:06:26 +02:00

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
}