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 a3 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.Method != "github.com/jmoiron/sqlx/reflectx.TestMustBe" { } 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) } } }