mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-22 13:00:19 +00: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
|
||
|
}
|