mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			261 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Faye Amacker. All rights reserved.
 | 
						|
// Licensed under the MIT License. See LICENSE in the project root for license information.
 | 
						|
 | 
						|
package cbor
 | 
						|
 | 
						|
import (
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
type field struct {
 | 
						|
	name               string
 | 
						|
	nameAsInt          int64 // used to decoder to match field name with CBOR int
 | 
						|
	cborName           []byte
 | 
						|
	cborNameByteString []byte // major type 2 name encoding iff cborName has major type 3
 | 
						|
	idx                []int
 | 
						|
	typ                reflect.Type
 | 
						|
	ef                 encodeFunc
 | 
						|
	ief                isEmptyFunc
 | 
						|
	typInfo            *typeInfo // used to decoder to reuse type info
 | 
						|
	tagged             bool      // used to choose dominant field (at the same level tagged fields dominate untagged fields)
 | 
						|
	omitEmpty          bool      // used to skip empty field
 | 
						|
	keyAsInt           bool      // used to encode/decode field name as int
 | 
						|
}
 | 
						|
 | 
						|
type fields []*field
 | 
						|
 | 
						|
// indexFieldSorter sorts fields by field idx at each level, breaking ties with idx depth.
 | 
						|
type indexFieldSorter struct {
 | 
						|
	fields fields
 | 
						|
}
 | 
						|
 | 
						|
func (x *indexFieldSorter) Len() int {
 | 
						|
	return len(x.fields)
 | 
						|
}
 | 
						|
 | 
						|
func (x *indexFieldSorter) Swap(i, j int) {
 | 
						|
	x.fields[i], x.fields[j] = x.fields[j], x.fields[i]
 | 
						|
}
 | 
						|
 | 
						|
func (x *indexFieldSorter) Less(i, j int) bool {
 | 
						|
	iIdx, jIdx := x.fields[i].idx, x.fields[j].idx
 | 
						|
	for k := 0; k < len(iIdx) && k < len(jIdx); k++ {
 | 
						|
		if iIdx[k] != jIdx[k] {
 | 
						|
			return iIdx[k] < jIdx[k]
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return len(iIdx) <= len(jIdx)
 | 
						|
}
 | 
						|
 | 
						|
// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag.
 | 
						|
type nameLevelAndTagFieldSorter struct {
 | 
						|
	fields fields
 | 
						|
}
 | 
						|
 | 
						|
func (x *nameLevelAndTagFieldSorter) Len() int {
 | 
						|
	return len(x.fields)
 | 
						|
}
 | 
						|
 | 
						|
func (x *nameLevelAndTagFieldSorter) Swap(i, j int) {
 | 
						|
	x.fields[i], x.fields[j] = x.fields[j], x.fields[i]
 | 
						|
}
 | 
						|
 | 
						|
func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool {
 | 
						|
	fi, fj := x.fields[i], x.fields[j]
 | 
						|
	if fi.name != fj.name {
 | 
						|
		return fi.name < fj.name
 | 
						|
	}
 | 
						|
	if len(fi.idx) != len(fj.idx) {
 | 
						|
		return len(fi.idx) < len(fj.idx)
 | 
						|
	}
 | 
						|
	if fi.tagged != fj.tagged {
 | 
						|
		return fi.tagged
 | 
						|
	}
 | 
						|
	return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
 | 
						|
}
 | 
						|
 | 
						|
// getFields returns visible fields of struct type t following visibility rules for JSON encoding.
 | 
						|
func getFields(t reflect.Type) (flds fields, structOptions string) {
 | 
						|
	// Get special field "_" tag options
 | 
						|
	if f, ok := t.FieldByName("_"); ok {
 | 
						|
		tag := f.Tag.Get("cbor")
 | 
						|
		if tag != "-" {
 | 
						|
			structOptions = tag
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// nTypes contains next level anonymous fields' types and indexes
 | 
						|
	// (there can be multiple fields of the same type at the same level)
 | 
						|
	flds, nTypes := appendFields(t, nil, nil, nil)
 | 
						|
 | 
						|
	if len(nTypes) > 0 {
 | 
						|
 | 
						|
		var cTypes map[reflect.Type][][]int      // current level anonymous fields' types and indexes
 | 
						|
		vTypes := map[reflect.Type]bool{t: true} // visited field types at less nested levels
 | 
						|
 | 
						|
		for len(nTypes) > 0 {
 | 
						|
			cTypes, nTypes = nTypes, nil
 | 
						|
 | 
						|
			for t, idx := range cTypes {
 | 
						|
				// If there are multiple anonymous fields of the same struct type at the same level, all are ignored.
 | 
						|
				if len(idx) > 1 {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				// Anonymous field of the same type at deeper nested level is ignored.
 | 
						|
				if vTypes[t] {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				vTypes[t] = true
 | 
						|
 | 
						|
				flds, nTypes = appendFields(t, idx[0], flds, nTypes)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	sort.Sort(&nameLevelAndTagFieldSorter{flds})
 | 
						|
 | 
						|
	// Keep visible fields.
 | 
						|
	j := 0 // index of next unique field
 | 
						|
	for i := 0; i < len(flds); {
 | 
						|
		name := flds[i].name
 | 
						|
		if i == len(flds)-1 || // last field
 | 
						|
			name != flds[i+1].name || // field i has unique field name
 | 
						|
			len(flds[i].idx) < len(flds[i+1].idx) || // field i is at a less nested level than field i+1
 | 
						|
			(flds[i].tagged && !flds[i+1].tagged) { // field i is tagged while field i+1 is not
 | 
						|
			flds[j] = flds[i]
 | 
						|
			j++
 | 
						|
		}
 | 
						|
 | 
						|
		// Skip fields with the same field name.
 | 
						|
		for i++; i < len(flds) && name == flds[i].name; i++ { //nolint:revive
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if j != len(flds) {
 | 
						|
		flds = flds[:j]
 | 
						|
	}
 | 
						|
 | 
						|
	// Sort fields by field index
 | 
						|
	sort.Sort(&indexFieldSorter{flds})
 | 
						|
 | 
						|
	return flds, structOptions
 | 
						|
}
 | 
						|
 | 
						|
// appendFields appends type t's exportable fields to flds and anonymous struct fields to nTypes .
 | 
						|
func appendFields(
 | 
						|
	t reflect.Type,
 | 
						|
	idx []int,
 | 
						|
	flds fields,
 | 
						|
	nTypes map[reflect.Type][][]int,
 | 
						|
) (
 | 
						|
	_flds fields,
 | 
						|
	_nTypes map[reflect.Type][][]int,
 | 
						|
) {
 | 
						|
	for i := 0; i < t.NumField(); i++ {
 | 
						|
		f := t.Field(i)
 | 
						|
 | 
						|
		ft := f.Type
 | 
						|
		for ft.Kind() == reflect.Ptr {
 | 
						|
			ft = ft.Elem()
 | 
						|
		}
 | 
						|
 | 
						|
		if !isFieldExportable(f, ft.Kind()) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		tag := f.Tag.Get("cbor")
 | 
						|
		if tag == "" {
 | 
						|
			tag = f.Tag.Get("json")
 | 
						|
		}
 | 
						|
		if tag == "-" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		tagged := tag != ""
 | 
						|
 | 
						|
		// Parse field tag options
 | 
						|
		var tagFieldName string
 | 
						|
		var omitempty, keyasint bool
 | 
						|
		for j := 0; tag != ""; j++ {
 | 
						|
			var token string
 | 
						|
			idx := strings.IndexByte(tag, ',')
 | 
						|
			if idx == -1 {
 | 
						|
				token, tag = tag, ""
 | 
						|
			} else {
 | 
						|
				token, tag = tag[:idx], tag[idx+1:]
 | 
						|
			}
 | 
						|
			if j == 0 {
 | 
						|
				tagFieldName = token
 | 
						|
			} else {
 | 
						|
				switch token {
 | 
						|
				case "omitempty":
 | 
						|
					omitempty = true
 | 
						|
				case "keyasint":
 | 
						|
					keyasint = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		fieldName := tagFieldName
 | 
						|
		if tagFieldName == "" {
 | 
						|
			fieldName = f.Name
 | 
						|
		}
 | 
						|
 | 
						|
		fIdx := make([]int, len(idx)+1)
 | 
						|
		copy(fIdx, idx)
 | 
						|
		fIdx[len(fIdx)-1] = i
 | 
						|
 | 
						|
		if !f.Anonymous || ft.Kind() != reflect.Struct || tagFieldName != "" {
 | 
						|
			flds = append(flds, &field{
 | 
						|
				name:      fieldName,
 | 
						|
				idx:       fIdx,
 | 
						|
				typ:       f.Type,
 | 
						|
				omitEmpty: omitempty,
 | 
						|
				keyAsInt:  keyasint,
 | 
						|
				tagged:    tagged})
 | 
						|
		} else {
 | 
						|
			if nTypes == nil {
 | 
						|
				nTypes = make(map[reflect.Type][][]int)
 | 
						|
			}
 | 
						|
			nTypes[ft] = append(nTypes[ft], fIdx)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return flds, nTypes
 | 
						|
}
 | 
						|
 | 
						|
// isFieldExportable returns true if f is an exportable (regular or anonymous) field or
 | 
						|
// a nonexportable anonymous field of struct type.
 | 
						|
// Nonexportable anonymous field of struct type can contain exportable fields.
 | 
						|
func isFieldExportable(f reflect.StructField, fk reflect.Kind) bool { //nolint:gocritic // ignore hugeParam
 | 
						|
	exportable := f.PkgPath == ""
 | 
						|
	return exportable || (f.Anonymous && fk == reflect.Struct)
 | 
						|
}
 | 
						|
 | 
						|
type embeddedFieldNullPtrFunc func(reflect.Value) (reflect.Value, error)
 | 
						|
 | 
						|
// getFieldValue returns field value of struct v by index.  When encountering null pointer
 | 
						|
// to anonymous (embedded) struct field, f is called with the last traversed field value.
 | 
						|
func getFieldValue(v reflect.Value, idx []int, f embeddedFieldNullPtrFunc) (fv reflect.Value, err error) {
 | 
						|
	fv = v
 | 
						|
	for i, n := range idx {
 | 
						|
		fv = fv.Field(n)
 | 
						|
 | 
						|
		if i < len(idx)-1 {
 | 
						|
			if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
 | 
						|
				if fv.IsNil() {
 | 
						|
					// Null pointer to embedded struct field
 | 
						|
					fv, err = f(fv)
 | 
						|
					if err != nil || !fv.IsValid() {
 | 
						|
						return fv, err
 | 
						|
					}
 | 
						|
				}
 | 
						|
				fv = fv.Elem()
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fv, nil
 | 
						|
}
 |