// 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 }