// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package protohcl import ( "fmt" "strings" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/anypb" ) const wellKnownTypeAny = "google.protobuf.Any" type AnyTypeProvider interface { AnyType(*UnmarshalContext, MessageDecoder) (protoreflect.FullName, MessageDecoder, error) } type AnyTypeURLProvider struct { TypeURLFieldName string } func (p *AnyTypeURLProvider) AnyType(ctx *UnmarshalContext, decoder MessageDecoder) (protoreflect.FullName, MessageDecoder, error) { typeURLFieldName := "type_url" if p != nil { typeURLFieldName = p.TypeURLFieldName } var typeURL *IterField err := decoder.EachField(FieldIterator{ Desc: (&anypb.Any{}).ProtoReflect().Descriptor(), Func: func(field *IterField) error { if field.Name == typeURLFieldName { typeURL = field } return nil }, IgnoreUnknown: true, }) if err != nil { return "", nil, err } if typeURL == nil || typeURL.Val == nil { return "", nil, fmt.Errorf("%s field is required to decode Any", typeURLFieldName) } url, err := stringFromCty(*typeURL.Val) if err != nil { return "", nil, err } slashIdx := strings.LastIndex(url, "/") typeName := url // strip all "hostname" parts of the URL path if slashIdx > 1 && slashIdx+1 < len(url) { typeName = url[slashIdx+1:] } return protoreflect.FullName(typeName), decoder.SkipFields(typeURLFieldName), nil } func (u UnmarshalOptions) decodeAny(ctx *UnmarshalContext, decoder MessageDecoder, msg protoreflect.Message) error { var typeProvider AnyTypeProvider = &AnyTypeURLProvider{TypeURLFieldName: "type_url"} if u.AnyTypeProvider != nil { typeProvider = u.AnyTypeProvider } var ( typeName protoreflect.FullName err error ) typeName, decoder, err = typeProvider.AnyType(ctx, decoder) if err != nil { return fmt.Errorf("error getting type for Any field: %w", err) } // the type.googleapis.come/ should be optional mt, err := protoregistry.GlobalTypes.FindMessageByName(typeName) if err != nil { return fmt.Errorf("error looking up type information for %s: %w", typeName, err) } newMsg := mt.New() err = u.decodeMessage(&UnmarshalContext{ Parent: ctx.Parent, Name: ctx.Name, Message: newMsg, }, decoder, newMsg) if err != nil { return err } enc, err := proto.Marshal(newMsg.Interface()) if err != nil { return fmt.Errorf("error marshalling Any data as protobuf value: %w", err) } anyValue := msg.Interface().(*anypb.Any) // This will look like . and not quite like a full URL with a path anyValue.TypeUrl = string(newMsg.Descriptor().FullName()) anyValue.Value = enc return nil } func isAnyField(desc protoreflect.FieldDescriptor) bool { if desc.Kind() != protoreflect.MessageKind { return false } return desc.Message().FullName() == wellKnownTypeAny }