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 (
|
|
|
|
"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))
|
|
|
|
}
|
|
|
|
}
|