package bind_test import ( "code.squareroundforest.org/arpio/bind" "code.squareroundforest.org/arpio/notation" "slices" "sort" "testing" ) func TestField(t *testing.T) { t.Run("fields", func(t *testing.T) { type s0 struct { FieldOne int } type s1 struct { Foo int } type s2 struct { s0 foo int FooBar int Baz s1 Qux *s1 Chan chan int Que bool Hola []int } f := bind.Fields[s2]() if len(f) != 6 { t.Fatal(notation.Sprintwt(f)) } m := make(map[string]bind.Field) for _, fi := range f { m[fi.Name()] = fi } fieldOne := m["field-one"] if !slices.Equal(fieldOne.Path(), []string{"FieldOne"}) { t.Fatal(fieldOne.Name()) } fooBar := m["foo-bar"] if !slices.Equal(fooBar.Path(), []string{"FooBar"}) { t.Fatal(fooBar.Name()) } bazFoo := m["baz-foo"] if !slices.Equal(bazFoo.Path(), []string{"Baz", "Foo"}) { t.Fatal(bazFoo.Name()) } quxFoo := m["qux-foo"] if !slices.Equal(quxFoo.Path(), []string{"Qux", "Foo"}) { t.Fatal(quxFoo.Name()) } que := m["que"] if !slices.Equal(que.Path(), []string{"Que"}) || !que.Bool() { t.Fatal(que.Name()) } hola := m["hola"] if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() { t.Fatal(hola.Name()) } t.Run("cannot have fields", func(t *testing.T) { type i []int f := bind.Fields[i]() if len(f) != 0 { t.Fatal() } }) t.Run("has circular type", func(t *testing.T) { type s struct{ Foo *s } if len(bind.Fields[s]()) != 0 { t.Fatal() } }) t.Run("list", func(t *testing.T) { type s struct{ Foo int } f := bind.Fields[[]s]() if len(f) != 1 || !f[0].List() { t.Fatal() } }) }) t.Run("field values", func(t *testing.T) { t.Run("has circular reference", func(t *testing.T) { type s struct{ Foo any } var v s v.Foo = v if len(bind.FieldValues(v)) != 0 { t.Fatal() } }) t.Run("slice", func(t *testing.T) { type s struct{ Foo int } f := bind.FieldValues([]s{{21}, {42}}) if len(f) != 2 || f[0].Value() != 21 || f[1].Value() != 42 { t.Fatal() } }) t.Run("scalar map", func(t *testing.T) { f := bind.FieldValues(map[string]int{"foo": 21, "bar": 42}) sort.Slice(f, func(i, j int) bool { if f[i].Name() > f[j].Name() { return true } if f[i].Value().(int) < f[j].Value().(int) { return true } return false }) if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || !f[0].Free() || f[1].Name() != "bar" || f[1].Value() != 42 || !f[1].Free() { t.Fatal(notation.Println(f)) } }) t.Run("scalar map with list values", func(t *testing.T) { f := bind.FieldValues(map[string][]int{"foo": []int{21, 36}, "bar": []int{42, 72}}) sort.Slice(f, func(i, j int) bool { if f[i].Name() > f[j].Name() { return true } if f[i].Value().(int) < f[j].Value().(int) { return true } return false }) if len(f) != 4 || f[0].Name() != "foo" || f[0].Value() != 21 || !f[0].Free() || f[1].Name() != "foo" || f[1].Value() != 36 || !f[1].Free() || f[2].Name() != "bar" || f[2].Value() != 42 || !f[2].Free() || f[3].Name() != "bar" || f[3].Value() != 72 || !f[3].Free() { t.Fatal(notation.Println(f)) } }) t.Run("not a struct", func(t *testing.T) { v := []int{21, 42, 84} f := bind.FieldValues(v) if len(f) != 0 { t.Fatal() } }) t.Run("not exported field", func(t *testing.T) { type s struct { Foo int bar string } v := s{Foo: 42, bar: "baz"} f := bind.FieldValues(v) if len(f) != 1 || f[0].Name() != "foo" { t.Fatal() } }) t.Run("scalar fields", func(t *testing.T) { type s struct { Foo int Bar bool } v := s{Foo: 42, Bar: true} f := bind.FieldValues(v) if len(f) != 2 || f[0].Name() != "foo" || !slices.Equal(f[0].Path(), []string{"Foo"}) || f[0].Value() != 42 || f[0].Bool() || f[1].Name() != "bar" || !slices.Equal(f[1].Path(), []string{"Bar"}) || f[1].Value() != true || !f[1.].Bool() { t.Fatal() } }) t.Run("list field", func(t *testing.T) { type s struct{ Foo []int } v := s{Foo: []int{21, 42, 84}} f := bind.FieldValues(v) if len(f) != 3 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "foo" || f[1].Value() != 42 || f[2].Name() != "foo" || f[2].Value() != 84 { t.Fatal(notation.Sprintwt(f)) } }) t.Run("list of lists", func(t *testing.T) { type s struct{ Foo [][]int } v := s{Foo: [][]int{{21}, {42}}} f := bind.FieldValues(v) if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "foo" || f[1].Value() != 42 { t.Fatal() } }) t.Run("list of scalar maps", func(t *testing.T) { type s struct{ Foo any } v := s{Foo: []any{map[string]int{"foo": 42}}} f := bind.FieldValues(v) if len(f) != 1 || f[0].Name() != "foo-foo" || f[0].Value() != 42 { t.Fatal(notation.Sprintwt(f)) } }) t.Run("list of structs", func(t *testing.T) { type s struct{ Foo any } v := s{Foo: []any{s{Foo: 21}, s{Foo: 42}}} f := bind.FieldValues(v) if len(f) != 2 || f[0].Name() != "foo-foo" || f[0].Value() != 21 || f[1].Name() != "foo-foo" || f[1].Value() != 42 { t.Fatal() } }) t.Run("scalar map field", func(t *testing.T) { type s struct{ Foo map[string]int } v := s{Foo: map[string]int{"foo": 42}} f := bind.FieldValues(v) if len(f) != 1 || f[0].Name() != "foo-foo" || f[0].Value() != 42 { t.Fatal() } }) t.Run("anonymous field", func(t *testing.T) { type s0 struct{ Foo int } type s1 struct { s0 Bar int } var v s1 v.Foo = 21 v.Bar = 42 f := bind.FieldValues(v) if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "bar" || f[1].Value() != 42 { t.Fatal() } }) t.Run("child struct", func(t *testing.T) { type S0 struct{ Foo int } type s1 struct { S0 S0 Bar int } var v s1 v.S0.Foo = 21 v.Bar = 42 f := bind.FieldValues(v) if len(f) != 2 || f[0].Name() != "s-0-foo" || f[0].Value() != 21 || f[1].Name() != "bar" || f[1].Value() != 42 { t.Fatal(notation.Sprintwt(f)) } }) }) }