consul/internal/protohcl/attributes.go

158 lines
4.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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
}