442 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			442 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Package reflectx implements extensions to the standard reflect lib suitable
 | 
						|
// for implementing marshalling and unmarshalling packages.  The main Mapper type
 | 
						|
// allows for Go-compatible named attribute access, including accessing embedded
 | 
						|
// struct attributes and the ability to use  functions and struct tags to
 | 
						|
// customize field names.
 | 
						|
//
 | 
						|
package reflectx
 | 
						|
 | 
						|
import (
 | 
						|
	"reflect"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
)
 | 
						|
 | 
						|
// A FieldInfo is metadata for a struct field.
 | 
						|
type FieldInfo struct {
 | 
						|
	Index    []int
 | 
						|
	Path     string
 | 
						|
	Field    reflect.StructField
 | 
						|
	Zero     reflect.Value
 | 
						|
	Name     string
 | 
						|
	Options  map[string]string
 | 
						|
	Embedded bool
 | 
						|
	Children []*FieldInfo
 | 
						|
	Parent   *FieldInfo
 | 
						|
}
 | 
						|
 | 
						|
// A StructMap is an index of field metadata for a struct.
 | 
						|
type StructMap struct {
 | 
						|
	Tree  *FieldInfo
 | 
						|
	Index []*FieldInfo
 | 
						|
	Paths map[string]*FieldInfo
 | 
						|
	Names map[string]*FieldInfo
 | 
						|
}
 | 
						|
 | 
						|
// GetByPath returns a *FieldInfo for a given string path.
 | 
						|
func (f StructMap) GetByPath(path string) *FieldInfo {
 | 
						|
	return f.Paths[path]
 | 
						|
}
 | 
						|
 | 
						|
// GetByTraversal returns a *FieldInfo for a given integer path.  It is
 | 
						|
// analogous to reflect.FieldByIndex, but using the cached traversal
 | 
						|
// rather than re-executing the reflect machinery each time.
 | 
						|
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
 | 
						|
	if len(index) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	tree := f.Tree
 | 
						|
	for _, i := range index {
 | 
						|
		if i >= len(tree.Children) || tree.Children[i] == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		tree = tree.Children[i]
 | 
						|
	}
 | 
						|
	return tree
 | 
						|
}
 | 
						|
 | 
						|
// Mapper is a general purpose mapper of names to struct fields.  A Mapper
 | 
						|
// behaves like most marshallers in the standard library, obeying a field tag
 | 
						|
// for name mapping but also providing a basic transform function.
 | 
						|
type Mapper struct {
 | 
						|
	cache      map[reflect.Type]*StructMap
 | 
						|
	tagName    string
 | 
						|
	tagMapFunc func(string) string
 | 
						|
	mapFunc    func(string) string
 | 
						|
	mutex      sync.Mutex
 | 
						|
}
 | 
						|
 | 
						|
// NewMapper returns a new mapper using the tagName as its struct field tag.
 | 
						|
// If tagName is the empty string, it is ignored.
 | 
						|
func NewMapper(tagName string) *Mapper {
 | 
						|
	return &Mapper{
 | 
						|
		cache:   make(map[reflect.Type]*StructMap),
 | 
						|
		tagName: tagName,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewMapperTagFunc returns a new mapper which contains a mapper for field names
 | 
						|
// AND a mapper for tag values.  This is useful for tags like json which can
 | 
						|
// have values like "name,omitempty".
 | 
						|
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
 | 
						|
	return &Mapper{
 | 
						|
		cache:      make(map[reflect.Type]*StructMap),
 | 
						|
		tagName:    tagName,
 | 
						|
		mapFunc:    mapFunc,
 | 
						|
		tagMapFunc: tagMapFunc,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewMapperFunc returns a new mapper which optionally obeys a field tag and
 | 
						|
// a struct field name mapper func given by f.  Tags will take precedence, but
 | 
						|
// for any other field, the mapped name will be f(field.Name)
 | 
						|
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
 | 
						|
	return &Mapper{
 | 
						|
		cache:   make(map[reflect.Type]*StructMap),
 | 
						|
		tagName: tagName,
 | 
						|
		mapFunc: f,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TypeMap returns a mapping of field strings to int slices representing
 | 
						|
// the traversal down the struct to reach the field.
 | 
						|
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
 | 
						|
	m.mutex.Lock()
 | 
						|
	mapping, ok := m.cache[t]
 | 
						|
	if !ok {
 | 
						|
		mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
 | 
						|
		m.cache[t] = mapping
 | 
						|
	}
 | 
						|
	m.mutex.Unlock()
 | 
						|
	return mapping
 | 
						|
}
 | 
						|
 | 
						|
// FieldMap returns the mapper's mapping of field names to reflect values.  Panics
 | 
						|
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
 | 
						|
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
 | 
						|
	v = reflect.Indirect(v)
 | 
						|
	mustBe(v, reflect.Struct)
 | 
						|
 | 
						|
	r := map[string]reflect.Value{}
 | 
						|
	tm := m.TypeMap(v.Type())
 | 
						|
	for tagName, fi := range tm.Names {
 | 
						|
		r[tagName] = FieldByIndexes(v, fi.Index)
 | 
						|
	}
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// FieldByName returns a field by its mapped name as a reflect.Value.
 | 
						|
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
 | 
						|
// Returns zero Value if the name is not found.
 | 
						|
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
 | 
						|
	v = reflect.Indirect(v)
 | 
						|
	mustBe(v, reflect.Struct)
 | 
						|
 | 
						|
	tm := m.TypeMap(v.Type())
 | 
						|
	fi, ok := tm.Names[name]
 | 
						|
	if !ok {
 | 
						|
		return v
 | 
						|
	}
 | 
						|
	return FieldByIndexes(v, fi.Index)
 | 
						|
}
 | 
						|
 | 
						|
// FieldsByName returns a slice of values corresponding to the slice of names
 | 
						|
// for the value.  Panics if v's Kind is not Struct or v is not Indirectable
 | 
						|
// to a struct Kind.  Returns zero Value for each name not found.
 | 
						|
func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
 | 
						|
	v = reflect.Indirect(v)
 | 
						|
	mustBe(v, reflect.Struct)
 | 
						|
 | 
						|
	tm := m.TypeMap(v.Type())
 | 
						|
	vals := make([]reflect.Value, 0, len(names))
 | 
						|
	for _, name := range names {
 | 
						|
		fi, ok := tm.Names[name]
 | 
						|
		if !ok {
 | 
						|
			vals = append(vals, *new(reflect.Value))
 | 
						|
		} else {
 | 
						|
			vals = append(vals, FieldByIndexes(v, fi.Index))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return vals
 | 
						|
}
 | 
						|
 | 
						|
// TraversalsByName returns a slice of int slices which represent the struct
 | 
						|
// traversals for each mapped name.  Panics if t is not a struct or Indirectable
 | 
						|
// to a struct.  Returns empty int slice for each name not found.
 | 
						|
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
 | 
						|
	r := make([][]int, 0, len(names))
 | 
						|
	m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
 | 
						|
		if i == nil {
 | 
						|
			r = append(r, []int{})
 | 
						|
		} else {
 | 
						|
			r = append(r, i)
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
 | 
						|
// each name and the struct traversal represented by that name. Panics if t is not
 | 
						|
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
 | 
						|
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
 | 
						|
	t = Deref(t)
 | 
						|
	mustBe(t, reflect.Struct)
 | 
						|
	tm := m.TypeMap(t)
 | 
						|
	for i, name := range names {
 | 
						|
		fi, ok := tm.Names[name]
 | 
						|
		if !ok {
 | 
						|
			if err := fn(i, nil); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if err := fn(i, fi.Index); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// FieldByIndexes returns a value for the field given by the struct traversal
 | 
						|
// for the given value.
 | 
						|
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
 | 
						|
	for _, i := range indexes {
 | 
						|
		v = reflect.Indirect(v).Field(i)
 | 
						|
		// if this is a pointer and it's nil, allocate a new value and set it
 | 
						|
		if v.Kind() == reflect.Ptr && v.IsNil() {
 | 
						|
			alloc := reflect.New(Deref(v.Type()))
 | 
						|
			v.Set(alloc)
 | 
						|
		}
 | 
						|
		if v.Kind() == reflect.Map && v.IsNil() {
 | 
						|
			v.Set(reflect.MakeMap(v.Type()))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
// FieldByIndexesReadOnly returns a value for a particular struct traversal,
 | 
						|
// but is not concerned with allocating nil pointers because the value is
 | 
						|
// going to be used for reading and not setting.
 | 
						|
func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
 | 
						|
	for _, i := range indexes {
 | 
						|
		v = reflect.Indirect(v).Field(i)
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
// Deref is Indirect for reflect.Types
 | 
						|
func Deref(t reflect.Type) reflect.Type {
 | 
						|
	if t.Kind() == reflect.Ptr {
 | 
						|
		t = t.Elem()
 | 
						|
	}
 | 
						|
	return t
 | 
						|
}
 | 
						|
 | 
						|
// -- helpers & utilities --
 | 
						|
 | 
						|
type kinder interface {
 | 
						|
	Kind() reflect.Kind
 | 
						|
}
 | 
						|
 | 
						|
// mustBe checks a value against a kind, panicing with a reflect.ValueError
 | 
						|
// if the kind isn't that which is required.
 | 
						|
func mustBe(v kinder, expected reflect.Kind) {
 | 
						|
	if k := v.Kind(); k != expected {
 | 
						|
		panic(&reflect.ValueError{Method: methodName(), Kind: k})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// methodName returns the caller of the function calling methodName
 | 
						|
func methodName() string {
 | 
						|
	pc, _, _, _ := runtime.Caller(2)
 | 
						|
	f := runtime.FuncForPC(pc)
 | 
						|
	if f == nil {
 | 
						|
		return "unknown method"
 | 
						|
	}
 | 
						|
	return f.Name()
 | 
						|
}
 | 
						|
 | 
						|
type typeQueue struct {
 | 
						|
	t  reflect.Type
 | 
						|
	fi *FieldInfo
 | 
						|
	pp string // Parent path
 | 
						|
}
 | 
						|
 | 
						|
// A copying append that creates a new slice each time.
 | 
						|
func apnd(is []int, i int) []int {
 | 
						|
	x := make([]int, len(is)+1)
 | 
						|
	for p, n := range is {
 | 
						|
		x[p] = n
 | 
						|
	}
 | 
						|
	x[len(x)-1] = i
 | 
						|
	return x
 | 
						|
}
 | 
						|
 | 
						|
type mapf func(string) string
 | 
						|
 | 
						|
// parseName parses the tag and the target name for the given field using
 | 
						|
// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
 | 
						|
// field's name to a target name, and tagMapFunc for mapping the tag to
 | 
						|
// a target name.
 | 
						|
func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
 | 
						|
	// first, set the fieldName to the field's name
 | 
						|
	fieldName = field.Name
 | 
						|
	// if a mapFunc is set, use that to override the fieldName
 | 
						|
	if mapFunc != nil {
 | 
						|
		fieldName = mapFunc(fieldName)
 | 
						|
	}
 | 
						|
 | 
						|
	// if there's no tag to look for, return the field name
 | 
						|
	if tagName == "" {
 | 
						|
		return "", fieldName
 | 
						|
	}
 | 
						|
 | 
						|
	// if this tag is not set using the normal convention in the tag,
 | 
						|
	// then return the fieldname..  this check is done because according
 | 
						|
	// to the reflect documentation:
 | 
						|
	//    If the tag does not have the conventional format,
 | 
						|
	//    the value returned by Get is unspecified.
 | 
						|
	// which doesn't sound great.
 | 
						|
	if !strings.Contains(string(field.Tag), tagName+":") {
 | 
						|
		return "", fieldName
 | 
						|
	}
 | 
						|
 | 
						|
	// at this point we're fairly sure that we have a tag, so lets pull it out
 | 
						|
	tag = field.Tag.Get(tagName)
 | 
						|
 | 
						|
	// if we have a mapper function, call it on the whole tag
 | 
						|
	// XXX: this is a change from the old version, which pulled out the name
 | 
						|
	// before the tagMapFunc could be run, but I think this is the right way
 | 
						|
	if tagMapFunc != nil {
 | 
						|
		tag = tagMapFunc(tag)
 | 
						|
	}
 | 
						|
 | 
						|
	// finally, split the options from the name
 | 
						|
	parts := strings.Split(tag, ",")
 | 
						|
	fieldName = parts[0]
 | 
						|
 | 
						|
	return tag, fieldName
 | 
						|
}
 | 
						|
 | 
						|
// parseOptions parses options out of a tag string, skipping the name
 | 
						|
func parseOptions(tag string) map[string]string {
 | 
						|
	parts := strings.Split(tag, ",")
 | 
						|
	options := make(map[string]string, len(parts))
 | 
						|
	if len(parts) > 1 {
 | 
						|
		for _, opt := range parts[1:] {
 | 
						|
			// short circuit potentially expensive split op
 | 
						|
			if strings.Contains(opt, "=") {
 | 
						|
				kv := strings.Split(opt, "=")
 | 
						|
				options[kv[0]] = kv[1]
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			options[opt] = ""
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return options
 | 
						|
}
 | 
						|
 | 
						|
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
 | 
						|
// tagMapFunc to determine the canonical names of fields.
 | 
						|
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
 | 
						|
	m := []*FieldInfo{}
 | 
						|
 | 
						|
	root := &FieldInfo{}
 | 
						|
	queue := []typeQueue{}
 | 
						|
	queue = append(queue, typeQueue{Deref(t), root, ""})
 | 
						|
 | 
						|
QueueLoop:
 | 
						|
	for len(queue) != 0 {
 | 
						|
		// pop the first item off of the queue
 | 
						|
		tq := queue[0]
 | 
						|
		queue = queue[1:]
 | 
						|
 | 
						|
		// ignore recursive field
 | 
						|
		for p := tq.fi.Parent; p != nil; p = p.Parent {
 | 
						|
			if tq.fi.Field.Type == p.Field.Type {
 | 
						|
				continue QueueLoop
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		nChildren := 0
 | 
						|
		if tq.t.Kind() == reflect.Struct {
 | 
						|
			nChildren = tq.t.NumField()
 | 
						|
		}
 | 
						|
		tq.fi.Children = make([]*FieldInfo, nChildren)
 | 
						|
 | 
						|
		// iterate through all of its fields
 | 
						|
		for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
 | 
						|
 | 
						|
			f := tq.t.Field(fieldPos)
 | 
						|
 | 
						|
			// parse the tag and the target name using the mapping options for this field
 | 
						|
			tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
 | 
						|
 | 
						|
			// if the name is "-", disabled via a tag, skip it
 | 
						|
			if name == "-" {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			fi := FieldInfo{
 | 
						|
				Field:   f,
 | 
						|
				Name:    name,
 | 
						|
				Zero:    reflect.New(f.Type).Elem(),
 | 
						|
				Options: parseOptions(tag),
 | 
						|
			}
 | 
						|
 | 
						|
			// if the path is empty this path is just the name
 | 
						|
			if tq.pp == "" {
 | 
						|
				fi.Path = fi.Name
 | 
						|
			} else {
 | 
						|
				fi.Path = tq.pp + "." + fi.Name
 | 
						|
			}
 | 
						|
 | 
						|
			// skip unexported fields
 | 
						|
			if len(f.PkgPath) != 0 && !f.Anonymous {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// bfs search of anonymous embedded structs
 | 
						|
			if f.Anonymous {
 | 
						|
				pp := tq.pp
 | 
						|
				if tag != "" {
 | 
						|
					pp = fi.Path
 | 
						|
				}
 | 
						|
 | 
						|
				fi.Embedded = true
 | 
						|
				fi.Index = apnd(tq.fi.Index, fieldPos)
 | 
						|
				nChildren := 0
 | 
						|
				ft := Deref(f.Type)
 | 
						|
				if ft.Kind() == reflect.Struct {
 | 
						|
					nChildren = ft.NumField()
 | 
						|
				}
 | 
						|
				fi.Children = make([]*FieldInfo, nChildren)
 | 
						|
				queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
 | 
						|
			} else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
 | 
						|
				fi.Index = apnd(tq.fi.Index, fieldPos)
 | 
						|
				fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
 | 
						|
				queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
 | 
						|
			}
 | 
						|
 | 
						|
			fi.Index = apnd(tq.fi.Index, fieldPos)
 | 
						|
			fi.Parent = tq.fi
 | 
						|
			tq.fi.Children[fieldPos] = &fi
 | 
						|
			m = append(m, &fi)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
 | 
						|
	for _, fi := range flds.Index {
 | 
						|
		flds.Paths[fi.Path] = fi
 | 
						|
		if fi.Name != "" && !fi.Embedded {
 | 
						|
			flds.Names[fi.Path] = fi
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return flds
 | 
						|
}
 |