reflectx: use github.com/scylladb/go-reflectx
Signed-off-by: Michał Matczuk <michal@scylladb.com>
This commit is contained in:
committed by
Michal Matczuk
parent
d20fd5b39c
commit
2348379a4f
2
Makefile
2
Makefile
@@ -27,7 +27,7 @@ test:
|
|||||||
@$(GOTEST) .
|
@$(GOTEST) .
|
||||||
@$(GOTEST) ./migrate
|
@$(GOTEST) ./migrate
|
||||||
@$(GOTEST) ./qb
|
@$(GOTEST) ./qb
|
||||||
@$(GOTEST) ./reflectx
|
@$(GOTEST) ./table
|
||||||
|
|
||||||
.PHONY: bench
|
.PHONY: bench
|
||||||
bench:
|
bench:
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -3,4 +3,5 @@ module github.com/scylladb/gocqlx
|
|||||||
require (
|
require (
|
||||||
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b
|
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b
|
||||||
github.com/google/go-cmp v0.2.0
|
github.com/google/go-cmp v0.2.0
|
||||||
|
github.com/scylladb/go-reflectx v1.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
"github.com/gocql/gocql"
|
||||||
"github.com/scylladb/gocqlx/reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// structOnlyError returns an error appropriate for type when a non-scannable
|
// structOnlyError returns an error appropriate for type when a non-scannable
|
||||||
|
|||||||
2
iterx.go
2
iterx.go
@@ -10,7 +10,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
"github.com/gocql/gocql"
|
||||||
"github.com/scylladb/gocqlx/reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get is a convenience function for creating iterator and calling Get.
|
// Get is a convenience function for creating iterator and calling Get.
|
||||||
|
|||||||
33
mapper.go
33
mapper.go
@@ -5,40 +5,11 @@
|
|||||||
package gocqlx
|
package gocqlx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/scylladb/go-reflectx"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/scylladb/gocqlx/reflectx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultMapper uses `db` tag and automatically converts struct field names to
|
// DefaultMapper uses `db` tag and automatically converts struct field names to
|
||||||
// snake case. It can be set to whatever you want, but it is encouraged to be
|
// snake case. It can be set to whatever you want, but it is encouraged to be
|
||||||
// set before gocqlx is used as name-to-field mappings are cached after first
|
// set before gocqlx is used as name-to-field mappings are cached after first
|
||||||
// use on a type.
|
// use on a type.
|
||||||
var DefaultMapper = reflectx.NewMapperFunc("db", snakeCase)
|
var DefaultMapper = reflectx.NewMapperFunc("db", reflectx.CamelToSnakeASCII)
|
||||||
|
|
||||||
// snakeCase converts camel case to snake case.
|
|
||||||
func snakeCase(s string) string {
|
|
||||||
buf := []byte(s)
|
|
||||||
out := make([]byte, 0, len(buf)+3)
|
|
||||||
|
|
||||||
l := len(buf)
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
if !(allowedBindRune(buf[i]) || buf[i] == '_') {
|
|
||||||
panic(fmt.Sprint("not allowed name ", s))
|
|
||||||
}
|
|
||||||
|
|
||||||
b := rune(buf[i])
|
|
||||||
|
|
||||||
if unicode.IsUpper(b) {
|
|
||||||
if i > 0 && buf[i-1] != '_' && (unicode.IsLower(rune(buf[i-1])) || (i+1 < l && unicode.IsLower(rune(buf[i+1])))) {
|
|
||||||
out = append(out, '_')
|
|
||||||
}
|
|
||||||
b = unicode.ToLower(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, byte(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(out)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocqlx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkSnakeCase(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
snakeCase(snakeTable[b.N%len(snakeTable)].N)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkToLower(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
strings.ToLower(snakeTable[b.N%len(snakeTable)].N)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
// Copyright (C) 2017 ScyllaDB
|
|
||||||
// Use of this source code is governed by a ALv2-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocqlx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var snakeTable = []struct {
|
|
||||||
N string
|
|
||||||
V string
|
|
||||||
}{
|
|
||||||
{"a", "a"},
|
|
||||||
{"snake", "snake"},
|
|
||||||
{"A", "a"},
|
|
||||||
{"ID", "id"},
|
|
||||||
{"MOTD", "motd"},
|
|
||||||
{"Snake", "snake"},
|
|
||||||
{"SnakeTest", "snake_test"},
|
|
||||||
{"APIResponse", "api_response"},
|
|
||||||
{"SnakeID", "snake_id"},
|
|
||||||
{"Snake_Id", "snake_id"},
|
|
||||||
{"Snake_ID", "snake_id"},
|
|
||||||
{"SnakeIDGoogle", "snake_id_google"},
|
|
||||||
{"LinuxMOTD", "linux_motd"},
|
|
||||||
{"OMGWTFBBQ", "omgwtfbbq"},
|
|
||||||
{"omg_wtf_bbq", "omg_wtf_bbq"},
|
|
||||||
{"woof_woof", "woof_woof"},
|
|
||||||
{"_woof_woof", "_woof_woof"},
|
|
||||||
{"woof_woof_", "woof_woof_"},
|
|
||||||
{"WOOF", "woof"},
|
|
||||||
{"Woof", "woof"},
|
|
||||||
{"woof", "woof"},
|
|
||||||
{"woof0_woof1", "woof0_woof1"},
|
|
||||||
{"_woof0_woof1_2", "_woof0_woof1_2"},
|
|
||||||
{"woof0_WOOF1_2", "woof0_woof1_2"},
|
|
||||||
{"WOOF0", "woof0"},
|
|
||||||
{"Woof1", "woof1"},
|
|
||||||
{"woof2", "woof2"},
|
|
||||||
{"woofWoof", "woof_woof"},
|
|
||||||
{"woofWOOF", "woof_woof"},
|
|
||||||
{"woof_WOOF", "woof_woof"},
|
|
||||||
{"Woof_WOOF", "woof_woof"},
|
|
||||||
{"WOOFWoofWoofWOOFWoofWoof", "woof_woof_woof_woof_woof_woof"},
|
|
||||||
{"WOOF_Woof_woof_WOOF_Woof_woof", "woof_woof_woof_woof_woof_woof"},
|
|
||||||
{"Woof_W", "woof_w"},
|
|
||||||
{"Woof_w", "woof_w"},
|
|
||||||
{"WoofW", "woof_w"},
|
|
||||||
{"Woof_W_", "woof_w_"},
|
|
||||||
{"Woof_w_", "woof_w_"},
|
|
||||||
{"WoofW_", "woof_w_"},
|
|
||||||
{"WOOF_", "woof_"},
|
|
||||||
{"W_Woof", "w_woof"},
|
|
||||||
{"w_Woof", "w_woof"},
|
|
||||||
{"WWoof", "w_woof"},
|
|
||||||
{"_W_Woof", "_w_woof"},
|
|
||||||
{"_w_Woof", "_w_woof"},
|
|
||||||
{"_WWoof", "_w_woof"},
|
|
||||||
{"_WOOF", "_woof"},
|
|
||||||
{"_woof", "_woof"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnakeCase(t *testing.T) {
|
|
||||||
for _, test := range snakeTable {
|
|
||||||
if actual := snakeCase(test.N); actual != test.V {
|
|
||||||
t.Error("V", test.V, "got", actual, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
"github.com/gocql/gocql"
|
||||||
"github.com/scylladb/gocqlx/reflectx"
|
"github.com/scylladb/go-reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompileNamedQuery translates query with named parameters in a form
|
// CompileNamedQuery translates query with named parameters in a form
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# reflectx
|
|
||||||
|
|
||||||
The sqlx package has special reflect needs. In particular, it needs to:
|
|
||||||
|
|
||||||
* be able to map a name to a field
|
|
||||||
* understand embedded structs
|
|
||||||
* understand mapping names to fields by a particular tag
|
|
||||||
* user specified name -> field mapping functions
|
|
||||||
|
|
||||||
These behaviors mimic the behaviors by the standard library marshallers and also the
|
|
||||||
behavior of standard Go accessors.
|
|
||||||
|
|
||||||
The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
|
|
||||||
addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
|
|
||||||
tags in the ways that are vital to most marshallers, and they are slow.
|
|
||||||
|
|
||||||
This reflectx package extends reflect to achieve these goals.
|
|
||||||
@@ -1,439 +0,0 @@
|
|||||||
// 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 { // nolint
|
|
||||||
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) { // nolint: unparam
|
|
||||||
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)
|
|
||||||
copy(x, is)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,971 +0,0 @@
|
|||||||
package reflectx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ival(v reflect.Value) int {
|
|
||||||
return v.Interface().(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
|
||||||
type Foo struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
C int
|
|
||||||
}
|
|
||||||
|
|
||||||
f := Foo{1, 2, 3}
|
|
||||||
fv := reflect.ValueOf(f)
|
|
||||||
m := NewMapperFunc("", func(s string) string { return s })
|
|
||||||
|
|
||||||
v := m.FieldByName(fv, "A")
|
|
||||||
if ival(v) != f.A {
|
|
||||||
t.Errorf("Expecting %d, got %d", ival(v), f.A)
|
|
||||||
}
|
|
||||||
v = m.FieldByName(fv, "B")
|
|
||||||
if ival(v) != f.B {
|
|
||||||
t.Errorf("Expecting %d, got %d", f.B, ival(v))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(fv, "C")
|
|
||||||
if ival(v) != f.C {
|
|
||||||
t.Errorf("Expecting %d, got %d", f.C, ival(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicEmbedded(t *testing.T) {
|
|
||||||
type Foo struct {
|
|
||||||
A int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bar struct {
|
|
||||||
Foo // `db:""` is implied for an embedded struct
|
|
||||||
B int
|
|
||||||
C int `db:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Baz struct {
|
|
||||||
A int
|
|
||||||
Bar `db:"Bar"`
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapperFunc("db", func(s string) string { return s })
|
|
||||||
|
|
||||||
z := Baz{}
|
|
||||||
z.A = 1
|
|
||||||
z.B = 2
|
|
||||||
z.C = 4
|
|
||||||
z.Bar.Foo.A = 3
|
|
||||||
|
|
||||||
zv := reflect.ValueOf(z)
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(z))
|
|
||||||
|
|
||||||
if len(fields.Index) != 5 {
|
|
||||||
t.Errorf("Expecting 5 fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for _, fi := range fields.Index {
|
|
||||||
// log.Println(fi)
|
|
||||||
// }
|
|
||||||
|
|
||||||
v := m.FieldByName(zv, "A")
|
|
||||||
if ival(v) != z.A {
|
|
||||||
t.Errorf("Expecting %d, got %d", z.A, ival(v))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(zv, "Bar.B")
|
|
||||||
if ival(v) != z.Bar.B {
|
|
||||||
t.Errorf("Expecting %d, got %d", z.Bar.B, ival(v))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(zv, "Bar.A")
|
|
||||||
if ival(v) != z.Bar.Foo.A {
|
|
||||||
t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(zv, "Bar.C")
|
|
||||||
if _, ok := v.Interface().(int); ok {
|
|
||||||
t.Errorf("Expecting Bar.C to not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
fi := fields.GetByPath("Bar.C")
|
|
||||||
if fi != nil {
|
|
||||||
t.Errorf("Bar.C should not exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmbeddedSimple(t *testing.T) {
|
|
||||||
type UUID [16]byte
|
|
||||||
type MyID struct {
|
|
||||||
UUID
|
|
||||||
}
|
|
||||||
type Item struct {
|
|
||||||
ID MyID
|
|
||||||
}
|
|
||||||
z := Item{}
|
|
||||||
|
|
||||||
m := NewMapper("db")
|
|
||||||
m.TypeMap(reflect.TypeOf(z))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicEmbeddedWithTags(t *testing.T) {
|
|
||||||
type Foo struct {
|
|
||||||
A int `db:"a"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bar struct {
|
|
||||||
Foo // `db:""` is implied for an embedded struct
|
|
||||||
B int `db:"b"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Baz struct {
|
|
||||||
A int `db:"a"`
|
|
||||||
Bar // `db:""` is implied for an embedded struct
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapper("db")
|
|
||||||
|
|
||||||
z := Baz{}
|
|
||||||
z.A = 1
|
|
||||||
z.B = 2
|
|
||||||
z.Bar.Foo.A = 3
|
|
||||||
|
|
||||||
zv := reflect.ValueOf(z)
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(z))
|
|
||||||
|
|
||||||
if len(fields.Index) != 5 {
|
|
||||||
t.Errorf("Expecting 5 fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for _, fi := range fields.index {
|
|
||||||
// log.Println(fi)
|
|
||||||
// }
|
|
||||||
|
|
||||||
v := m.FieldByName(zv, "a")
|
|
||||||
if ival(v) != z.Bar.Foo.A { // the dominant field
|
|
||||||
t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(zv, "b")
|
|
||||||
if ival(v) != z.B {
|
|
||||||
t.Errorf("Expecting %d, got %d", z.B, ival(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFlatTags(t *testing.T) {
|
|
||||||
m := NewMapper("db")
|
|
||||||
|
|
||||||
type Asset struct {
|
|
||||||
Title string `db:"title"`
|
|
||||||
}
|
|
||||||
type Post struct {
|
|
||||||
Author string `db:"author,required"`
|
|
||||||
Asset Asset `db:""`
|
|
||||||
}
|
|
||||||
// Post columns: (author title)
|
|
||||||
|
|
||||||
post := Post{Author: "Joe", Asset: Asset{Title: "Hello"}}
|
|
||||||
pv := reflect.ValueOf(post)
|
|
||||||
|
|
||||||
v := m.FieldByName(pv, "author")
|
|
||||||
if v.Interface().(string) != post.Author {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "title")
|
|
||||||
if v.Interface().(string) != post.Asset.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNestedStruct(t *testing.T) {
|
|
||||||
m := NewMapper("db")
|
|
||||||
|
|
||||||
type Details struct {
|
|
||||||
Active bool `db:"active"`
|
|
||||||
}
|
|
||||||
type Asset struct {
|
|
||||||
Title string `db:"title"`
|
|
||||||
Details Details `db:"details"`
|
|
||||||
}
|
|
||||||
type Post struct {
|
|
||||||
Author string `db:"author,required"`
|
|
||||||
Asset `db:"asset"`
|
|
||||||
}
|
|
||||||
// Post columns: (author asset.title asset.details.active)
|
|
||||||
|
|
||||||
post := Post{
|
|
||||||
Author: "Joe",
|
|
||||||
Asset: Asset{Title: "Hello", Details: Details{Active: true}},
|
|
||||||
}
|
|
||||||
pv := reflect.ValueOf(post)
|
|
||||||
|
|
||||||
v := m.FieldByName(pv, "author")
|
|
||||||
if v.Interface().(string) != post.Author {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "title")
|
|
||||||
if _, ok := v.Interface().(string); ok {
|
|
||||||
t.Errorf("Expecting field to not exist")
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "asset.title")
|
|
||||||
if v.Interface().(string) != post.Asset.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "asset.details.active")
|
|
||||||
if v.Interface().(bool) != post.Asset.Details.Active {
|
|
||||||
t.Errorf("Expecting %v, got %v", post.Asset.Details.Active, v.Interface().(bool))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInlineStruct(t *testing.T) {
|
|
||||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
|
||||||
|
|
||||||
type Employee struct {
|
|
||||||
Name string
|
|
||||||
ID int
|
|
||||||
}
|
|
||||||
type Boss Employee
|
|
||||||
type person struct {
|
|
||||||
Employee `db:"employee"`
|
|
||||||
Boss `db:"boss"`
|
|
||||||
}
|
|
||||||
// employees columns: (employee.name employee.id boss.name boss.id)
|
|
||||||
|
|
||||||
em := person{Employee: Employee{Name: "Joe", ID: 2}, Boss: Boss{Name: "Dick", ID: 1}}
|
|
||||||
ev := reflect.ValueOf(em)
|
|
||||||
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(em))
|
|
||||||
if len(fields.Index) != 6 {
|
|
||||||
t.Errorf("Expecting 6 fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := m.FieldByName(ev, "employee.name")
|
|
||||||
if v.Interface().(string) != em.Employee.Name {
|
|
||||||
t.Errorf("Expecting %s, got %s", em.Employee.Name, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(ev, "boss.id")
|
|
||||||
if ival(v) != em.Boss.ID {
|
|
||||||
t.Errorf("Expecting %v, got %v", em.Boss.ID, ival(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecursiveStruct(t *testing.T) {
|
|
||||||
type Person struct {
|
|
||||||
Parent *Person
|
|
||||||
}
|
|
||||||
m := NewMapperFunc("db", strings.ToLower)
|
|
||||||
var p *Person
|
|
||||||
m.TypeMap(reflect.TypeOf(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldsEmbedded(t *testing.T) {
|
|
||||||
m := NewMapper("db")
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string `db:"name,size=64"`
|
|
||||||
}
|
|
||||||
type Place struct {
|
|
||||||
Name string `db:"name"`
|
|
||||||
}
|
|
||||||
type Article struct {
|
|
||||||
Title string `db:"title"`
|
|
||||||
}
|
|
||||||
type PP struct {
|
|
||||||
Person `db:"person,required"`
|
|
||||||
Place `db:",someflag"`
|
|
||||||
Article `db:",required"`
|
|
||||||
}
|
|
||||||
// PP columns: (person.name name title)
|
|
||||||
|
|
||||||
pp := PP{}
|
|
||||||
pp.Person.Name = "Peter"
|
|
||||||
pp.Place.Name = "Toronto"
|
|
||||||
pp.Article.Title = "Best city ever"
|
|
||||||
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(pp))
|
|
||||||
// for i, f := range fields {
|
|
||||||
// log.Println(i, f)
|
|
||||||
// }
|
|
||||||
|
|
||||||
ppv := reflect.ValueOf(pp)
|
|
||||||
|
|
||||||
v := m.FieldByName(ppv, "person.name")
|
|
||||||
if v.Interface().(string) != pp.Person.Name {
|
|
||||||
t.Errorf("Expecting %s, got %s", pp.Person.Name, v.Interface().(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
v = m.FieldByName(ppv, "name")
|
|
||||||
if v.Interface().(string) != pp.Place.Name {
|
|
||||||
t.Errorf("Expecting %s, got %s", pp.Place.Name, v.Interface().(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
v = m.FieldByName(ppv, "title")
|
|
||||||
if v.Interface().(string) != pp.Article.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", pp.Article.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
fi := fields.GetByPath("person")
|
|
||||||
if _, ok := fi.Options["required"]; !ok {
|
|
||||||
t.Errorf("Expecting required option to be set")
|
|
||||||
}
|
|
||||||
if !fi.Embedded {
|
|
||||||
t.Errorf("Expecting field to be embedded")
|
|
||||||
}
|
|
||||||
if len(fi.Index) != 1 || fi.Index[0] != 0 {
|
|
||||||
t.Errorf("Expecting index to be [0]")
|
|
||||||
}
|
|
||||||
|
|
||||||
fi = fields.GetByPath("person.name")
|
|
||||||
if fi == nil {
|
|
||||||
t.Errorf("Expecting person.name to exist")
|
|
||||||
}
|
|
||||||
if fi.Path != "person.name" {
|
|
||||||
t.Errorf("Expecting %s, got %s", "person.name", fi.Path)
|
|
||||||
}
|
|
||||||
if fi.Options["size"] != "64" {
|
|
||||||
t.Errorf("Expecting %s, got %s", "64", fi.Options["size"])
|
|
||||||
}
|
|
||||||
|
|
||||||
fi = fields.GetByTraversal([]int{1, 0})
|
|
||||||
if fi == nil {
|
|
||||||
t.Errorf("Expecting traveral to exist")
|
|
||||||
}
|
|
||||||
if fi.Path != "name" {
|
|
||||||
t.Errorf("Expecting %s, got %s", "name", fi.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fi = fields.GetByTraversal([]int{2})
|
|
||||||
if fi == nil {
|
|
||||||
t.Errorf("Expecting traversal to exist")
|
|
||||||
}
|
|
||||||
if _, ok := fi.Options["required"]; !ok {
|
|
||||||
t.Errorf("Expecting required option to be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"})
|
|
||||||
if !reflect.DeepEqual(trs, [][]int{{0, 0}, {1, 0}, {2, 0}}) {
|
|
||||||
t.Errorf("Expecting traversal: %v", trs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPtrFields(t *testing.T) {
|
|
||||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
|
||||||
type Asset struct {
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
type Post struct {
|
|
||||||
*Asset `db:"asset"`
|
|
||||||
Author string
|
|
||||||
}
|
|
||||||
|
|
||||||
post := &Post{Author: "Joe", Asset: &Asset{Title: "Hiyo"}}
|
|
||||||
pv := reflect.ValueOf(post)
|
|
||||||
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(post))
|
|
||||||
if len(fields.Index) != 3 {
|
|
||||||
t.Errorf("Expecting 3 fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := m.FieldByName(pv, "asset.title")
|
|
||||||
if v.Interface().(string) != post.Asset.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "author")
|
|
||||||
if v.Interface().(string) != post.Author {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNamedPtrFields(t *testing.T) {
|
|
||||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Asset struct {
|
|
||||||
Title string
|
|
||||||
|
|
||||||
Owner *User `db:"owner"`
|
|
||||||
}
|
|
||||||
type Post struct {
|
|
||||||
Author string
|
|
||||||
|
|
||||||
Asset1 *Asset `db:"asset1"`
|
|
||||||
Asset2 *Asset `db:"asset2"`
|
|
||||||
}
|
|
||||||
|
|
||||||
post := &Post{Author: "Joe", Asset1: &Asset{Title: "Hiyo", Owner: &User{"Username"}}} // Let Asset2 be nil
|
|
||||||
pv := reflect.ValueOf(post)
|
|
||||||
|
|
||||||
fields := m.TypeMap(reflect.TypeOf(post))
|
|
||||||
if len(fields.Index) != 9 {
|
|
||||||
t.Errorf("Expecting 9 fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := m.FieldByName(pv, "asset1.title")
|
|
||||||
if v.Interface().(string) != post.Asset1.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset1.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "asset1.owner.name")
|
|
||||||
if v.Interface().(string) != post.Asset1.Owner.Name {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset1.Owner.Name, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "asset2.title")
|
|
||||||
if v.Interface().(string) != post.Asset2.Title {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset2.Title, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "asset2.owner.name")
|
|
||||||
if v.Interface().(string) != post.Asset2.Owner.Name {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Asset2.Owner.Name, v.Interface().(string))
|
|
||||||
}
|
|
||||||
v = m.FieldByName(pv, "author")
|
|
||||||
if v.Interface().(string) != post.Author {
|
|
||||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldMap(t *testing.T) {
|
|
||||||
type Foo struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
C int
|
|
||||||
}
|
|
||||||
|
|
||||||
f := Foo{1, 2, 3}
|
|
||||||
m := NewMapperFunc("db", strings.ToLower)
|
|
||||||
|
|
||||||
fm := m.FieldMap(reflect.ValueOf(f))
|
|
||||||
|
|
||||||
if len(fm) != 3 {
|
|
||||||
t.Errorf("Expecting %d keys, got %d", 3, len(fm))
|
|
||||||
}
|
|
||||||
if fm["a"].Interface().(int) != 1 {
|
|
||||||
t.Errorf("Expecting %d, got %d", 1, ival(fm["a"]))
|
|
||||||
}
|
|
||||||
if fm["b"].Interface().(int) != 2 {
|
|
||||||
t.Errorf("Expecting %d, got %d", 2, ival(fm["b"]))
|
|
||||||
}
|
|
||||||
if fm["c"].Interface().(int) != 3 {
|
|
||||||
t.Errorf("Expecting %d, got %d", 3, ival(fm["c"]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTagNameMapping(t *testing.T) {
|
|
||||||
type Strategy struct {
|
|
||||||
StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"`
|
|
||||||
StrategyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string {
|
|
||||||
if strings.Contains(value, ",") {
|
|
||||||
return strings.Split(value, ",")[0]
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
})
|
|
||||||
strategy := Strategy{"1", "Alpah"}
|
|
||||||
mapping := m.TypeMap(reflect.TypeOf(strategy))
|
|
||||||
|
|
||||||
for _, key := range []string{"strategy_id", "STRATEGYNAME"} {
|
|
||||||
if fi := mapping.GetByPath(key); fi == nil {
|
|
||||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapping(t *testing.T) {
|
|
||||||
type Person struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
WearsGlasses bool `db:"wears_glasses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapperFunc("db", strings.ToLower)
|
|
||||||
p := Person{1, "Jason", true}
|
|
||||||
mapping := m.TypeMap(reflect.TypeOf(p))
|
|
||||||
|
|
||||||
for _, key := range []string{"id", "name", "wears_glasses"} {
|
|
||||||
if fi := mapping.GetByPath(key); fi == nil {
|
|
||||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SportsPerson struct {
|
|
||||||
Weight int
|
|
||||||
Age int
|
|
||||||
Person
|
|
||||||
}
|
|
||||||
s := SportsPerson{Weight: 100, Age: 30, Person: p}
|
|
||||||
mapping = m.TypeMap(reflect.TypeOf(s))
|
|
||||||
for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} {
|
|
||||||
if fi := mapping.GetByPath(key); fi == nil {
|
|
||||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RugbyPlayer struct {
|
|
||||||
Position int
|
|
||||||
IsIntense bool `db:"is_intense"`
|
|
||||||
IsAllBlack bool `db:"-"`
|
|
||||||
SportsPerson
|
|
||||||
}
|
|
||||||
r := RugbyPlayer{12, true, false, s}
|
|
||||||
mapping = m.TypeMap(reflect.TypeOf(r))
|
|
||||||
for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} {
|
|
||||||
if fi := mapping.GetByPath(key); fi == nil {
|
|
||||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi := mapping.GetByPath("isallblack"); fi != nil {
|
|
||||||
t.Errorf("Expecting to ignore `IsAllBlack` field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetByTraversal(t *testing.T) {
|
|
||||||
type C struct {
|
|
||||||
C0 int
|
|
||||||
C1 int
|
|
||||||
}
|
|
||||||
type B struct {
|
|
||||||
B0 string
|
|
||||||
B1 *C
|
|
||||||
}
|
|
||||||
type A struct {
|
|
||||||
A0 int
|
|
||||||
A1 B
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
Index []int
|
|
||||||
ExpectedName string
|
|
||||||
ExpectNil bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Index: []int{0},
|
|
||||||
ExpectedName: "A0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Index: []int{1, 0},
|
|
||||||
ExpectedName: "B0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Index: []int{1, 1, 1},
|
|
||||||
ExpectedName: "C1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Index: []int{3, 4, 5},
|
|
||||||
ExpectNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Index: []int{},
|
|
||||||
ExpectNil: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Index: nil,
|
|
||||||
ExpectNil: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapperFunc("db", func(n string) string { return n })
|
|
||||||
tm := m.TypeMap(reflect.TypeOf(A{}))
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
fi := tm.GetByTraversal(tc.Index)
|
|
||||||
if tc.ExpectNil {
|
|
||||||
if fi != nil {
|
|
||||||
t.Errorf("%d: expected nil, got %v", i, fi)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi == nil {
|
|
||||||
t.Errorf("%d: expected %s, got nil", i, tc.ExpectedName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Name != tc.ExpectedName {
|
|
||||||
t.Errorf("%d: expected %s, got %s", i, tc.ExpectedName, fi.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMapperMethodsByName tests Mapper methods FieldByName and TraversalsByName
|
|
||||||
func TestMapperMethodsByName(t *testing.T) {
|
|
||||||
type C struct {
|
|
||||||
C0 string
|
|
||||||
C1 int
|
|
||||||
}
|
|
||||||
type B struct {
|
|
||||||
B0 *C `db:"B0"`
|
|
||||||
B1 C `db:"B1"`
|
|
||||||
B2 string `db:"B2"`
|
|
||||||
}
|
|
||||||
type A struct {
|
|
||||||
A0 *B `db:"A0"`
|
|
||||||
B `db:"A1"`
|
|
||||||
A2 int
|
|
||||||
}
|
|
||||||
|
|
||||||
val := &A{
|
|
||||||
A0: &B{
|
|
||||||
B0: &C{C0: "0", C1: 1},
|
|
||||||
B1: C{C0: "2", C1: 3},
|
|
||||||
B2: "4",
|
|
||||||
},
|
|
||||||
B: B{
|
|
||||||
B0: nil,
|
|
||||||
B1: C{C0: "5", C1: 6},
|
|
||||||
B2: "7",
|
|
||||||
},
|
|
||||||
A2: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
Name string
|
|
||||||
ExpectInvalid bool
|
|
||||||
ExpectedValue interface{}
|
|
||||||
ExpectedIndexes []int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "A0.B0.C0",
|
|
||||||
ExpectedValue: "0",
|
|
||||||
ExpectedIndexes: []int{0, 0, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A0.B0.C1",
|
|
||||||
ExpectedValue: 1,
|
|
||||||
ExpectedIndexes: []int{0, 0, 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A0.B1.C0",
|
|
||||||
ExpectedValue: "2",
|
|
||||||
ExpectedIndexes: []int{0, 1, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A0.B1.C1",
|
|
||||||
ExpectedValue: 3,
|
|
||||||
ExpectedIndexes: []int{0, 1, 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A0.B2",
|
|
||||||
ExpectedValue: "4",
|
|
||||||
ExpectedIndexes: []int{0, 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A1.B0.C0",
|
|
||||||
ExpectedValue: "",
|
|
||||||
ExpectedIndexes: []int{1, 0, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A1.B0.C1",
|
|
||||||
ExpectedValue: 0,
|
|
||||||
ExpectedIndexes: []int{1, 0, 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A1.B1.C0",
|
|
||||||
ExpectedValue: "5",
|
|
||||||
ExpectedIndexes: []int{1, 1, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A1.B1.C1",
|
|
||||||
ExpectedValue: 6,
|
|
||||||
ExpectedIndexes: []int{1, 1, 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A1.B2",
|
|
||||||
ExpectedValue: "7",
|
|
||||||
ExpectedIndexes: []int{1, 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "A2",
|
|
||||||
ExpectedValue: 8,
|
|
||||||
ExpectedIndexes: []int{2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "XYZ",
|
|
||||||
ExpectInvalid: true,
|
|
||||||
ExpectedIndexes: []int{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "a3",
|
|
||||||
ExpectInvalid: true,
|
|
||||||
ExpectedIndexes: []int{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the names array from the test cases
|
|
||||||
names := make([]string, len(testCases))
|
|
||||||
for i, tc := range testCases {
|
|
||||||
names[i] = tc.Name
|
|
||||||
}
|
|
||||||
m := NewMapperFunc("db", func(n string) string { return n })
|
|
||||||
v := reflect.ValueOf(val)
|
|
||||||
values := m.FieldsByName(v, names)
|
|
||||||
if len(values) != len(testCases) {
|
|
||||||
t.Errorf("expected %d values, got %d", len(testCases), len(values))
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
indexes := m.TraversalsByName(v.Type(), names)
|
|
||||||
if len(indexes) != len(testCases) {
|
|
||||||
t.Errorf("expected %d traversals, got %d", len(testCases), len(indexes))
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
for i, val := range values {
|
|
||||||
tc := testCases[i]
|
|
||||||
traversal := indexes[i]
|
|
||||||
if !reflect.DeepEqual(tc.ExpectedIndexes, traversal) {
|
|
||||||
t.Errorf("expected %v, got %v", tc.ExpectedIndexes, traversal)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
val = reflect.Indirect(val)
|
|
||||||
if tc.ExpectInvalid {
|
|
||||||
if val.IsValid() {
|
|
||||||
t.Errorf("%d: expected zero value, got %v", i, val)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !val.IsValid() {
|
|
||||||
t.Errorf("%d: expected valid value, got %v", i, val)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
actualValue := reflect.Indirect(val).Interface()
|
|
||||||
if !reflect.DeepEqual(tc.ExpectedValue, actualValue) {
|
|
||||||
t.Errorf("%d: expected %v, got %v", i, tc.ExpectedValue, actualValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldByIndexes(t *testing.T) {
|
|
||||||
type C struct {
|
|
||||||
C0 bool
|
|
||||||
C1 string
|
|
||||||
C2 int
|
|
||||||
C3 map[string]int
|
|
||||||
}
|
|
||||||
type B struct {
|
|
||||||
B1 C
|
|
||||||
B2 *C
|
|
||||||
}
|
|
||||||
type A struct {
|
|
||||||
A1 B
|
|
||||||
A2 *B
|
|
||||||
}
|
|
||||||
testCases := []struct {
|
|
||||||
value interface{}
|
|
||||||
indexes []int
|
|
||||||
expectedValue interface{}
|
|
||||||
readOnly bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
value: A{
|
|
||||||
A1: B{B1: C{C0: true}},
|
|
||||||
},
|
|
||||||
indexes: []int{0, 0, 0},
|
|
||||||
expectedValue: true,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: A{
|
|
||||||
A2: &B{B2: &C{C1: "answer"}},
|
|
||||||
},
|
|
||||||
indexes: []int{1, 1, 1},
|
|
||||||
expectedValue: "answer",
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: &A{},
|
|
||||||
indexes: []int{1, 1, 3},
|
|
||||||
expectedValue: map[string]int{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
checkResults := func(v reflect.Value) {
|
|
||||||
if tc.expectedValue == nil {
|
|
||||||
if !v.IsNil() {
|
|
||||||
t.Errorf("%d: expected nil, actual %v", i, v.Interface())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !reflect.DeepEqual(tc.expectedValue, v.Interface()) {
|
|
||||||
t.Errorf("%d: expected %v, actual %v", i, tc.expectedValue, v.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResults(FieldByIndexes(reflect.ValueOf(tc.value), tc.indexes))
|
|
||||||
if tc.readOnly {
|
|
||||||
checkResults(FieldByIndexesReadOnly(reflect.ValueOf(tc.value), tc.indexes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMustBe(t *testing.T) {
|
|
||||||
typ := reflect.TypeOf(E1{})
|
|
||||||
mustBe(typ, reflect.Struct)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
valueErr, ok := r.(*reflect.ValueError)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("unexpected Method: %s", valueErr.Method)
|
|
||||||
t.Error("expected panic with *reflect.ValueError")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if valueErr.Kind != reflect.String {
|
|
||||||
t.Errorf("unexpected Kind: %s", valueErr.Kind)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Error("expected panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
typ = reflect.TypeOf("string")
|
|
||||||
mustBe(typ, reflect.Struct)
|
|
||||||
t.Error("got here, didn't expect to")
|
|
||||||
}
|
|
||||||
|
|
||||||
type E1 struct {
|
|
||||||
A int
|
|
||||||
}
|
|
||||||
type E2 struct {
|
|
||||||
E1
|
|
||||||
B int
|
|
||||||
}
|
|
||||||
type E3 struct {
|
|
||||||
E2
|
|
||||||
C int
|
|
||||||
}
|
|
||||||
type E4 struct {
|
|
||||||
E3
|
|
||||||
D int
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFieldNameL1(b *testing.B) {
|
|
||||||
e4 := E4{D: 1}
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v := reflect.ValueOf(e4)
|
|
||||||
f := v.FieldByName("D")
|
|
||||||
if f.Interface().(int) != 1 {
|
|
||||||
b.Fatal("Wrong value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFieldNameL4(b *testing.B) {
|
|
||||||
e4 := E4{}
|
|
||||||
e4.A = 1
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v := reflect.ValueOf(e4)
|
|
||||||
f := v.FieldByName("A")
|
|
||||||
if f.Interface().(int) != 1 {
|
|
||||||
b.Fatal("Wrong value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFieldPosL1(b *testing.B) {
|
|
||||||
e4 := E4{D: 1}
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v := reflect.ValueOf(e4)
|
|
||||||
f := v.Field(1)
|
|
||||||
if f.Interface().(int) != 1 {
|
|
||||||
b.Fatal("Wrong value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFieldPosL4(b *testing.B) {
|
|
||||||
e4 := E4{}
|
|
||||||
e4.A = 1
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v := reflect.ValueOf(e4)
|
|
||||||
f := v.Field(0)
|
|
||||||
f = f.Field(0)
|
|
||||||
f = f.Field(0)
|
|
||||||
f = f.Field(0)
|
|
||||||
if f.Interface().(int) != 1 {
|
|
||||||
b.Fatal("Wrong value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFieldByIndexL4(b *testing.B) {
|
|
||||||
e4 := E4{}
|
|
||||||
e4.A = 1
|
|
||||||
idx := []int{0, 0, 0, 0}
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v := reflect.ValueOf(e4)
|
|
||||||
f := FieldByIndexes(v, idx)
|
|
||||||
if f.Interface().(int) != 1 {
|
|
||||||
b.Fatal("Wrong value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkTraversalsByName(b *testing.B) {
|
|
||||||
type A struct {
|
|
||||||
Value int
|
|
||||||
}
|
|
||||||
|
|
||||||
type B struct {
|
|
||||||
A A
|
|
||||||
}
|
|
||||||
|
|
||||||
type C struct {
|
|
||||||
B B
|
|
||||||
}
|
|
||||||
|
|
||||||
type D struct {
|
|
||||||
C C
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapper("")
|
|
||||||
t := reflect.TypeOf(D{})
|
|
||||||
names := []string{"C", "B", "A", "Value"}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if l := len(m.TraversalsByName(t, names)); l != len(names) {
|
|
||||||
b.Errorf("expected %d values, got %d", len(names), l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkTraversalsByNameFunc(b *testing.B) {
|
|
||||||
type A struct {
|
|
||||||
Z int
|
|
||||||
}
|
|
||||||
|
|
||||||
type B struct {
|
|
||||||
A A
|
|
||||||
}
|
|
||||||
|
|
||||||
type C struct {
|
|
||||||
B B
|
|
||||||
}
|
|
||||||
|
|
||||||
type D struct {
|
|
||||||
C C
|
|
||||||
}
|
|
||||||
|
|
||||||
m := NewMapper("")
|
|
||||||
t := reflect.TypeOf(D{})
|
|
||||||
names := []string{"C", "B", "A", "Z", "Y"}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
var l int
|
|
||||||
|
|
||||||
if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error {
|
|
||||||
l++
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
b.Errorf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if l != len(names) {
|
|
||||||
b.Errorf("expected %d values, got %d", len(names), l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user