// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package protohcl import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclparse" "github.com/zclconf/go-cty/cty/function" "google.golang.org/protobuf/reflect/protoreflect" ) // UnmarshalContext provides information about the context in which we are // unmarshalling HCL. It is primarily used for decoding Any fields based on // surrounding information (e.g. the resource Type block). type UnmarshalContext struct { // Parent context. Parent *UnmarshalContext // Name of the field that we are unmarshalling. Name string // Message is the protobuf message that we are unmarshalling into (may be nil). Message protoreflect.Message // Range of where this field was in the HCL source. Range hcl.Range } // ErrorRange returns a range that can be used in error messages. func (ctx *UnmarshalContext) ErrorRange() hcl.Range { for { if !ctx.Range.Empty() || ctx.Parent == nil { return ctx.Range } ctx = ctx.Parent } } func Unmarshal(src []byte, dest protoreflect.ProtoMessage) error { return UnmarshalOptions{}.Unmarshal(src, dest) } type UnmarshalOptions struct { AnyTypeProvider AnyTypeProvider SourceFileName string FieldNamer FieldNamer Functions map[string]function.Function } func (u UnmarshalOptions) Unmarshal(src []byte, dest protoreflect.ProtoMessage) error { rmsg := dest.ProtoReflect() file, diags := hclparse.NewParser().ParseHCL(src, u.SourceFileName) // error performing basic HCL parsing if diags.HasErrors() { return diags } u.clearAll(rmsg) if u.FieldNamer == nil { u.FieldNamer = textFieldNamer{} } return u.decodeMessage( &UnmarshalContext{Message: rmsg}, u.bodyDecoder(file.Body), rmsg, ) } func (u UnmarshalOptions) bodyDecoder(body hcl.Body) MessageDecoder { return newBodyDecoder(body, u.FieldNamer, u.Functions) } func (u UnmarshalOptions) decodeMessage(ctx *UnmarshalContext, decoder MessageDecoder, msg protoreflect.Message) error { desc := msg.Descriptor() if desc.FullName() == wellKnownTypeAny { return u.decodeAny(ctx, decoder, msg) } tracker := newOneOfTracker(u.FieldNamer) return decoder.EachField(FieldIterator{ Desc: desc, Func: func(field *IterField) error { if err := tracker.markFieldAsSet(field.Desc); err != nil { return err } var ( protoVal protoreflect.Value err error ) switch { case field.Val != nil: protoVal, err = u.decodeAttribute( &UnmarshalContext{ Parent: ctx, Name: field.Name, Range: field.Range, }, func() protoreflect.Value { return msg.NewField(field.Desc) }, field.Desc, *field.Val, true, ) case len(field.Blocks) != 0: protoVal, err = u.decodeBlocks(ctx, field.Blocks, msg, field.Desc) default: panic("decoder yielded no blocks or attributes") } if err != nil { return err } if protoVal.IsValid() { msg.Set(field.Desc, protoVal) } return nil }, }) } type newMessageFn func() protoreflect.Value func (u UnmarshalOptions) clearAll(msg protoreflect.Message) { fields := msg.Descriptor().Fields() for i := 0; i < fields.Len(); i++ { msg.Clear(fields.Get(i)) } }