consul/internal/protohcl/unmarshal.go

138 lines
3.2 KiB
Go

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