mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			186 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package gohcl
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/hashicorp/hcl/v2"
 | 
						|
)
 | 
						|
 | 
						|
// ImpliedBodySchema produces a hcl.BodySchema derived from the type of the
 | 
						|
// given value, which must be a struct value or a pointer to one. If an
 | 
						|
// inappropriate value is passed, this function will panic.
 | 
						|
//
 | 
						|
// The second return argument indicates whether the given struct includes
 | 
						|
// a "remain" field, and thus the returned schema is non-exhaustive.
 | 
						|
//
 | 
						|
// This uses the tags on the fields of the struct to discover how each
 | 
						|
// field's value should be expressed within configuration. If an invalid
 | 
						|
// mapping is attempted, this function will panic.
 | 
						|
func ImpliedBodySchema(val any) (schema *hcl.BodySchema, partial bool) {
 | 
						|
	ty := reflect.TypeOf(val)
 | 
						|
 | 
						|
	if ty.Kind() == reflect.Ptr {
 | 
						|
		ty = ty.Elem()
 | 
						|
	}
 | 
						|
 | 
						|
	if ty.Kind() != reflect.Struct {
 | 
						|
		panic(fmt.Sprintf("given value must be struct, not %T", val))
 | 
						|
	}
 | 
						|
 | 
						|
	var attrSchemas []hcl.AttributeSchema
 | 
						|
	var blockSchemas []hcl.BlockHeaderSchema
 | 
						|
 | 
						|
	tags := getFieldTags(ty)
 | 
						|
 | 
						|
	attrNames := make([]string, 0, len(tags.Attributes))
 | 
						|
	for n := range tags.Attributes {
 | 
						|
		attrNames = append(attrNames, n)
 | 
						|
	}
 | 
						|
	sort.Strings(attrNames)
 | 
						|
	for _, n := range attrNames {
 | 
						|
		idx := tags.Attributes[n]
 | 
						|
		optional := tags.Optional[n]
 | 
						|
		field := ty.Field(idx)
 | 
						|
 | 
						|
		var required bool
 | 
						|
 | 
						|
		switch {
 | 
						|
		case field.Type.AssignableTo(exprType):
 | 
						|
			//nolint:misspell
 | 
						|
			// If we're decoding to hcl.Expression then absense can be
 | 
						|
			// indicated via a null value, so we don't specify that
 | 
						|
			// the field is required during decoding.
 | 
						|
			required = false
 | 
						|
		case field.Type.Kind() != reflect.Ptr && !optional:
 | 
						|
			required = true
 | 
						|
		default:
 | 
						|
			required = false
 | 
						|
		}
 | 
						|
 | 
						|
		attrSchemas = append(attrSchemas, hcl.AttributeSchema{
 | 
						|
			Name:     n,
 | 
						|
			Required: required,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	blockNames := make([]string, 0, len(tags.Blocks))
 | 
						|
	for n := range tags.Blocks {
 | 
						|
		blockNames = append(blockNames, n)
 | 
						|
	}
 | 
						|
	sort.Strings(blockNames)
 | 
						|
	for _, n := range blockNames {
 | 
						|
		idx := tags.Blocks[n]
 | 
						|
		field := ty.Field(idx)
 | 
						|
		fty := field.Type
 | 
						|
		if fty.Kind() == reflect.Slice {
 | 
						|
			fty = fty.Elem()
 | 
						|
		}
 | 
						|
		if fty.Kind() == reflect.Ptr {
 | 
						|
			fty = fty.Elem()
 | 
						|
		}
 | 
						|
		if fty.Kind() != reflect.Struct {
 | 
						|
			panic(fmt.Sprintf(
 | 
						|
				"hcl 'block' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
 | 
						|
			))
 | 
						|
		}
 | 
						|
		ftags := getFieldTags(fty)
 | 
						|
		var labelNames []string
 | 
						|
		if len(ftags.Labels) > 0 {
 | 
						|
			labelNames = make([]string, len(ftags.Labels))
 | 
						|
			for i, l := range ftags.Labels {
 | 
						|
				labelNames[i] = l.Name
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		blockSchemas = append(blockSchemas, hcl.BlockHeaderSchema{
 | 
						|
			Type:       n,
 | 
						|
			LabelNames: labelNames,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	partial = tags.Remain != nil
 | 
						|
	schema = &hcl.BodySchema{
 | 
						|
		Attributes: attrSchemas,
 | 
						|
		Blocks:     blockSchemas,
 | 
						|
	}
 | 
						|
	return schema, partial
 | 
						|
}
 | 
						|
 | 
						|
type fieldTags struct {
 | 
						|
	Attributes map[string]int
 | 
						|
	Blocks     map[string]int
 | 
						|
	Labels     []labelField
 | 
						|
	Remain     *int
 | 
						|
	Body       *int
 | 
						|
	Optional   map[string]bool
 | 
						|
}
 | 
						|
 | 
						|
type labelField struct {
 | 
						|
	FieldIndex int
 | 
						|
	Name       string
 | 
						|
}
 | 
						|
 | 
						|
func getFieldTags(ty reflect.Type) *fieldTags {
 | 
						|
	ret := &fieldTags{
 | 
						|
		Attributes: map[string]int{},
 | 
						|
		Blocks:     map[string]int{},
 | 
						|
		Optional:   map[string]bool{},
 | 
						|
	}
 | 
						|
 | 
						|
	ct := ty.NumField()
 | 
						|
	for i := range ct {
 | 
						|
		field := ty.Field(i)
 | 
						|
		tag := field.Tag.Get("hcl")
 | 
						|
		if tag == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		comma := strings.Index(tag, ",")
 | 
						|
		var name, kind string
 | 
						|
		if comma != -1 {
 | 
						|
			name = tag[:comma]
 | 
						|
			kind = tag[comma+1:]
 | 
						|
		} else {
 | 
						|
			name = tag
 | 
						|
			kind = "attr"
 | 
						|
		}
 | 
						|
 | 
						|
		switch kind {
 | 
						|
		case "attr":
 | 
						|
			ret.Attributes[name] = i
 | 
						|
		case "block":
 | 
						|
			ret.Blocks[name] = i
 | 
						|
		case "label":
 | 
						|
			ret.Labels = append(ret.Labels, labelField{
 | 
						|
				FieldIndex: i,
 | 
						|
				Name:       name,
 | 
						|
			})
 | 
						|
		case "remain":
 | 
						|
			if ret.Remain != nil {
 | 
						|
				panic("only one 'remain' tag is permitted")
 | 
						|
			}
 | 
						|
			idx := i // copy, because this loop will continue assigning to i
 | 
						|
			ret.Remain = &idx
 | 
						|
		case "body":
 | 
						|
			if ret.Body != nil {
 | 
						|
				panic("only one 'body' tag is permitted")
 | 
						|
			}
 | 
						|
			idx := i // copy, because this loop will continue assigning to i
 | 
						|
			ret.Body = &idx
 | 
						|
		case "optional":
 | 
						|
			ret.Attributes[name] = i
 | 
						|
			ret.Optional[name] = true
 | 
						|
		default:
 | 
						|
			panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ret
 | 
						|
}
 |