2023-08-22 22:23:54 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
2023-08-11 19:52:51 +00:00
|
|
|
package protohcl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (u UnmarshalOptions) decodeAttribute(ctx *UnmarshalContext, newMessage newMessageFn, f protoreflect.FieldDescriptor, val cty.Value, listAllowed bool) (protoreflect.Value, error) {
|
|
|
|
if f.IsMap() {
|
|
|
|
return u.decodeAttributeToMap(ctx, newMessage, f, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.IsList() && listAllowed {
|
|
|
|
return u.decodeAttributeToList(ctx, newMessage, f, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
ok, value, err := decodeAttributeToWellKnownType(f, val)
|
|
|
|
if ok {
|
|
|
|
return value, errors.Wrapf(err, "%s: Failed to unmarshal argument %s", ctx.ErrorRange(), ctx.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
ok, value, err = u.decodeAttributeToMessage(ctx, newMessage, f, val)
|
|
|
|
if ok {
|
|
|
|
return value, err
|
|
|
|
}
|
|
|
|
|
|
|
|
value, err = decodeAttributeToPrimitive(f, val)
|
|
|
|
if err != nil {
|
|
|
|
return value, errors.Wrapf(err, "%s: Failed to unmarshal argument %s", ctx.ErrorRange(), ctx.Name)
|
|
|
|
}
|
|
|
|
return value, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u UnmarshalOptions) decodeAttributeToMessage(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, val cty.Value) (bool, protoreflect.Value, error) {
|
|
|
|
if desc.Kind() != protoreflect.MessageKind {
|
|
|
|
return false, protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := newMessage().Message()
|
|
|
|
|
|
|
|
ctx = &UnmarshalContext{
|
|
|
|
Parent: ctx.Parent,
|
|
|
|
Name: ctx.Name,
|
|
|
|
Message: msg,
|
|
|
|
Range: ctx.Range,
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have limited support for HCL functions, essentially just those that
|
|
|
|
// return a protobuf message (like the resource `gvk` function) in which
|
|
|
|
// case, the message will be wrapped in a cty capsule.
|
|
|
|
if val.Type().IsCapsuleType() {
|
|
|
|
msg, ok := val.EncapsulatedValue().(proto.Message)
|
|
|
|
if ok {
|
|
|
|
return true, protoreflect.ValueOf(msg.ProtoReflect()), nil
|
|
|
|
} else {
|
|
|
|
return true, protoreflect.Value{}, fmt.Errorf("expected encapsulated value to be a message, actual type: %T", val.EncapsulatedValue())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !val.Type().IsObjectType() {
|
|
|
|
return false, protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder := newObjectDecoder(val, u.FieldNamer, ctx.ErrorRange())
|
|
|
|
|
|
|
|
if err := u.decodeMessage(ctx, decoder, msg); err != nil {
|
|
|
|
return true, protoreflect.Value{}, err
|
|
|
|
}
|
|
|
|
return true, protoreflect.ValueOf(msg), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u UnmarshalOptions) decodeAttributeToList(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, value cty.Value) (protoreflect.Value, error) {
|
|
|
|
if value.IsNull() {
|
|
|
|
return protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
valueType := value.Type()
|
|
|
|
if !valueType.IsListType() && !valueType.IsTupleType() {
|
|
|
|
return protoreflect.Value{}, fmt.Errorf("expected list/tuple type after HCL decode but the actual type was %s", valueType.FriendlyName())
|
|
|
|
}
|
|
|
|
|
|
|
|
if value.LengthInt() < 1 {
|
|
|
|
return protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
protoList := newMessage().List()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
var idx int
|
|
|
|
value.ForEachElement(func(_ cty.Value, val cty.Value) bool {
|
|
|
|
var protoVal protoreflect.Value
|
|
|
|
protoVal, err = u.decodeAttribute(&UnmarshalContext{
|
|
|
|
Parent: ctx,
|
|
|
|
Name: fmt.Sprintf("%s[%d]", u.FieldNamer.NameField(desc), idx),
|
|
|
|
}, protoList.NewElement, desc, val, false)
|
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
idx++
|
|
|
|
protoList.Append(protoVal)
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return protoreflect.Value{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return protoreflect.ValueOfList(protoList), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u UnmarshalOptions) decodeAttributeToMap(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, value cty.Value) (protoreflect.Value, error) {
|
|
|
|
if value.IsNull() {
|
|
|
|
return protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
valueType := value.Type()
|
|
|
|
if !valueType.IsMapType() && !valueType.IsObjectType() {
|
|
|
|
return protoreflect.Value{}, fmt.Errorf("expected map/object type after HCL decode but the actual type was %s", valueType.FriendlyName())
|
|
|
|
}
|
|
|
|
|
|
|
|
if value.LengthInt() < 1 {
|
|
|
|
return protoreflect.Value{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
protoMap := newMessage().Map()
|
|
|
|
protoValueDesc := desc.MapValue()
|
|
|
|
var err error
|
|
|
|
|
|
|
|
value.ForEachElement(func(key cty.Value, val cty.Value) (stop bool) {
|
|
|
|
var protoVal protoreflect.Value
|
|
|
|
protoVal, err = u.decodeAttribute(&UnmarshalContext{
|
|
|
|
Parent: ctx,
|
|
|
|
Name: fmt.Sprintf("%s[%q]", u.FieldNamer.NameField(desc), key.AsString()),
|
|
|
|
Message: nil, // TODO: what should this really be?
|
|
|
|
}, protoMap.NewValue, protoValueDesc, val, false)
|
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if protoVal.IsValid() {
|
|
|
|
// HCL doesn't support non-string keyed maps so we blindly use string keys
|
|
|
|
protoMap.Set(protoreflect.ValueOfString(key.AsString()).MapKey(), protoVal)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return protoreflect.Value{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return protoreflect.ValueOfMap(protoMap), nil
|
|
|
|
}
|