2025-08-28 05:04:06 +02:00
|
|
|
package bind_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"code.squareroundforest.org/arpio/bind"
|
|
|
|
|
"code.squareroundforest.org/arpio/notation"
|
2025-09-04 00:33:05 +02:00
|
|
|
"fmt"
|
|
|
|
|
"reflect"
|
2025-08-28 05:04:06 +02:00
|
|
|
"slices"
|
|
|
|
|
"sort"
|
2025-08-31 17:11:09 +02:00
|
|
|
"strings"
|
2025-08-28 05:04:06 +02:00
|
|
|
"testing"
|
2025-09-03 23:52:33 +02:00
|
|
|
"time"
|
2025-08-28 05:04:06 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestField(t *testing.T) {
|
2025-08-31 17:11:09 +02:00
|
|
|
sortFields := func(f []bind.Field, p ...func(int, int) bool) {
|
|
|
|
|
sort.Slice(f, func(i, j int) bool {
|
|
|
|
|
pi, pj := strings.Join(f[i].Path(), ":"), strings.Join(f[j].Path(), ":")
|
|
|
|
|
if pi < pj {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[i].Name() < f[j].Name() {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, pi := range p {
|
|
|
|
|
if pi(i, j) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 05:04:06 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
t.Run("mixed", func(t *testing.T) {
|
|
|
|
|
f := bind.Fields[s2]()
|
|
|
|
|
if len(f) != 6 {
|
|
|
|
|
t.Fatal(notation.Sprintwt(f))
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
m := make(map[string]bind.Field)
|
|
|
|
|
for _, fi := range f {
|
|
|
|
|
m[fi.Name()] = fi
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
fieldOne := m["field-one"]
|
|
|
|
|
if !slices.Equal(fieldOne.Path(), []string{"FieldOne"}) {
|
|
|
|
|
t.Fatal(fieldOne.Name())
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
fooBar := m["foo-bar"]
|
|
|
|
|
if !slices.Equal(fooBar.Path(), []string{"FooBar"}) {
|
|
|
|
|
t.Fatal(fooBar.Name())
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
bazFoo := m["baz-foo"]
|
|
|
|
|
if !slices.Equal(bazFoo.Path(), []string{"Baz", "Foo"}) {
|
|
|
|
|
t.Fatal(bazFoo.Name())
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
quxFoo := m["qux-foo"]
|
|
|
|
|
if !slices.Equal(quxFoo.Path(), []string{"Qux", "Foo"}) {
|
|
|
|
|
t.Fatal(quxFoo.Name())
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
que := m["que"]
|
|
|
|
|
if !slices.Equal(que.Path(), []string{"Que"}) || que.Type() != bind.Bool {
|
|
|
|
|
t.Fatal(que.Name())
|
|
|
|
|
}
|
2025-08-28 05:04:06 +02:00
|
|
|
|
2025-09-03 22:12:13 +02:00
|
|
|
hola := m["hola"]
|
|
|
|
|
if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() {
|
|
|
|
|
t.Fatal(hola.Name())
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-28 05:04:06 +02:00
|
|
|
|
|
|
|
|
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]()
|
2025-09-03 22:12:13 +02:00
|
|
|
if len(f) != 1 || f[0].List() {
|
2025-08-28 05:04:06 +02:00
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 22:12:13 +02:00
|
|
|
|
|
|
|
|
t.Run("anonymous fields", func(t *testing.T) {
|
|
|
|
|
type s0 struct{ Foo int }
|
|
|
|
|
type s1 struct {
|
|
|
|
|
s0
|
|
|
|
|
Bar int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f := bind.Fields[s1]()
|
|
|
|
|
if len(f) != 2 || f[0].Name() != "foo" || f[1].Name() != "bar" {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 22:46:09 +02:00
|
|
|
|
|
|
|
|
t.Run("list of struct", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
type s struct{ Foo []struct{ Bar int } }
|
2025-09-03 22:46:09 +02:00
|
|
|
f := bind.Fields[s]()
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo-bar" || !f[0].List() {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 23:52:33 +02:00
|
|
|
|
|
|
|
|
t.Run("field types", func(t *testing.T) {
|
|
|
|
|
type s struct {
|
|
|
|
|
B bool
|
|
|
|
|
I int
|
|
|
|
|
U uint
|
|
|
|
|
F float64
|
|
|
|
|
S string
|
|
|
|
|
D time.Duration
|
|
|
|
|
T time.Time
|
|
|
|
|
A any
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f := bind.Fields[s]()
|
|
|
|
|
if len(f) != 8 {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 00:33:05 +02:00
|
|
|
check := map[string]bind.FieldType{
|
2025-09-03 23:52:33 +02:00
|
|
|
"b": bind.Bool,
|
|
|
|
|
"i": bind.Int,
|
|
|
|
|
"u": bind.Uint,
|
|
|
|
|
"f": bind.Float64,
|
|
|
|
|
"s": bind.String,
|
|
|
|
|
"d": bind.Duration,
|
|
|
|
|
"t": bind.Time,
|
|
|
|
|
"a": bind.Any,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, fi := range f {
|
|
|
|
|
if fi.Type() != check[fi.Name()] {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-04 00:33:05 +02:00
|
|
|
|
|
|
|
|
t.Run("fields from reflection type", func(t *testing.T) {
|
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo int }
|
|
|
|
|
f := bind.FieldsOf(reflect.TypeFor[s]())
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("nil type", func(t *testing.T) {
|
|
|
|
|
f := bind.FieldsOf(nil)
|
|
|
|
|
if len(f) != 0 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("circular type", func(t *testing.T) {
|
|
|
|
|
type p *p
|
|
|
|
|
f := bind.FieldsOf(reflect.TypeFor[p]())
|
|
|
|
|
if len(f) != 0 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-08-28 05:04:06 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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
|
2025-09-03 22:12:13 +02:00
|
|
|
v.Foo = &v
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 0 {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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})
|
2025-08-31 17:11:09 +02:00
|
|
|
sortFields(f)
|
2025-08-28 05:04:06 +02:00
|
|
|
if len(f) != 2 ||
|
2025-08-31 17:11:09 +02:00
|
|
|
f[0].Name() != "bar" || f[0].Value() != 42 || !f[0].Free() ||
|
|
|
|
|
f[1].Name() != "foo" || f[1].Value() != 21 || !f[1].Free() {
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Fatal(notation.Sprint(f))
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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}})
|
2025-08-31 17:11:09 +02:00
|
|
|
sortFields(f)
|
2025-08-28 05:04:06 +02:00
|
|
|
if len(f) != 4 ||
|
2025-08-31 17:11:09 +02:00
|
|
|
f[0].Name() != "bar" || f[0].Value() != 42 || !f[0].Free() ||
|
|
|
|
|
f[1].Name() != "bar" || f[1].Value() != 72 || !f[1].Free() ||
|
|
|
|
|
f[2].Name() != "foo" || f[2].Value() != 21 || !f[2].Free() ||
|
|
|
|
|
f[3].Name() != "foo" || f[3].Value() != 36 || !f[3].Free() {
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Fatal(notation.Sprint(f))
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("not a struct", func(t *testing.T) {
|
|
|
|
|
v := []int{21, 42, 84}
|
|
|
|
|
f := bind.FieldValues(v)
|
2025-09-03 22:12:13 +02:00
|
|
|
if len(f) != 0 {
|
2025-08-28 05:04:06 +02:00
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("not exported field", func(t *testing.T) {
|
|
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
bar string
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
|
2025-08-28 05:04:06 +02:00
|
|
|
v := s{Foo: 42, bar: "baz"}
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" {
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Fatal(notation.Sprint(f))
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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 ||
|
2025-08-31 02:56:26 +02:00
|
|
|
f[0].Name() != "foo" || !slices.Equal(f[0].Path(), []string{"Foo"}) || f[0].Value() != 42 || f[0].Type() == bind.Bool ||
|
|
|
|
|
f[1].Name() != "bar" || !slices.Equal(f[1].Path(), []string{"Bar"}) || f[1].Value() != true || f[1.].Type() != bind.Bool {
|
2025-08-28 05:04:06 +02:00
|
|
|
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)
|
2025-09-03 22:12:13 +02:00
|
|
|
if len(f) != 1 || f[0].Name() != "foo" || f[0].Value() != nil {
|
2025-08-28 05:04:06 +02:00
|
|
|
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)
|
2025-09-03 22:12:13 +02:00
|
|
|
if len(f) != 1 ||
|
|
|
|
|
f[0].Name() != "foo" || f[0].Value() != nil {
|
2025-08-28 05:04:06 +02:00
|
|
|
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
|
|
|
|
|
}
|
2025-09-03 22:12:13 +02:00
|
|
|
|
2025-08-28 05:04:06 +02:00
|
|
|
var v s1
|
|
|
|
|
v.Foo = 21
|
|
|
|
|
v.Bar = 42
|
|
|
|
|
f := bind.FieldValues(v)
|
2025-09-03 22:12:13 +02:00
|
|
|
if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "bar" || f[1].Value() != 42 {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 22:12:13 +02:00
|
|
|
|
|
|
|
|
t.Run("any type field nil", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo any }
|
|
|
|
|
var v s
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" || f[0].Type() != bind.Any {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("any type field not nil", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo any }
|
|
|
|
|
v := s{42}
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" || f[0].Type() != bind.Any || f[0].Value() != 42 {
|
|
|
|
|
t.Fatal(notation.Sprintt(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slice field empty", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo []int }
|
|
|
|
|
var v s
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Int || f[0].Value() != nil {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slice field not empty", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo []int }
|
|
|
|
|
v := s{[]int{21, 42, 84}}
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 3 ||
|
|
|
|
|
f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Int || f[0].Value() != 21 ||
|
|
|
|
|
f[1].Name() != "foo" || !f[1].List() || f[1].Type() != bind.Int || f[1].Value() != 42 ||
|
|
|
|
|
f[2].Name() != "foo" || !f[2].List() || f[2].Type() != bind.Int || f[2].Value() != 84 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slice field with any type empty", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo []any }
|
|
|
|
|
var v s
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Any || f[0].Value() != nil {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("slice field with any type not empty", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo []any }
|
|
|
|
|
v := s{[]any{21, 42, 84}}
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 3 ||
|
|
|
|
|
f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Any || f[0].Value() != 21 ||
|
|
|
|
|
f[1].Name() != "foo" || !f[1].List() || f[1].Type() != bind.Any || f[1].Value() != 42 ||
|
|
|
|
|
f[2].Name() != "foo" || !f[2].List() || f[2].Type() != bind.Any || f[2].Value() != 84 {
|
|
|
|
|
t.Fatal(notation.Sprintw(f))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("struct in its own field via interface", func(t *testing.T) {
|
|
|
|
|
var s struct{ F any }
|
|
|
|
|
s.F = s
|
|
|
|
|
f := bind.FieldValues(s)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "f" || f[0].Value() != nil {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 22:46:09 +02:00
|
|
|
|
|
|
|
|
t.Run("list of struct", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
type s struct{ Foo []struct{ Bar int } }
|
2025-09-03 22:46:09 +02:00
|
|
|
var v s
|
|
|
|
|
f := bind.FieldValues(v)
|
|
|
|
|
if len(f) != 1 || f[0].Name() != "foo-bar" || !f[0].List() {
|
|
|
|
|
t.Fatal(notation.Sprint(f))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
|
|
|
f := bind.FieldValues(nil)
|
|
|
|
|
if len(f) != 0 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-28 05:04:06 +02:00
|
|
|
})
|
2025-08-31 00:40:47 +02:00
|
|
|
|
|
|
|
|
t.Run("bind fields", func(t *testing.T) {
|
|
|
|
|
t.Run("no circular receiver", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo *s
|
|
|
|
|
Bar int
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
if len(bind.Bind(&v, bind.NamedValue("bar", 42))) != 1 {
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("no circular valeu", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo int }
|
2025-08-31 00:40:47 +02:00
|
|
|
type p *p
|
|
|
|
|
var v p
|
2025-09-04 00:33:05 +02:00
|
|
|
if len(bind.Bind(&v, bind.NamedValue("foo", 42))) != 1 {
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("set by name", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ FooBar int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || v.FooBar != 42 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("set by path", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ FooBar int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.ValueByPath([]string{"FooBar"}, 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || v.FooBar != 42 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("fail to bind", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar int
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 21),
|
|
|
|
|
bind.NamedValue("baz", 42),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 1 || u[0].Name() != "baz" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bind list", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo []int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 21),
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("foo", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || v.Foo[0] != 21 || v.Foo[1] != 42 || v.Foo[2] != 84 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bind list of structs", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo []struct{ Bar int } }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo-bar", 21),
|
|
|
|
|
bind.NamedValue("foo-bar", 42),
|
|
|
|
|
bind.NamedValue("foo-bar", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || len(v.Foo) != 3 || v.Foo[0].Bar != 21 || v.Foo[1].Bar != 42 || v.Foo[2].Bar != 84 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bind list in list", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo []struct{ Bar []int } }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo-bar", 21),
|
|
|
|
|
bind.NamedValue("foo-bar", 42),
|
|
|
|
|
bind.NamedValue("foo-bar", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || len(v.Foo) != 3 ||
|
|
|
|
|
len(v.Foo[0].Bar) != 1 || v.Foo[0].Bar[0] != 21 ||
|
|
|
|
|
len(v.Foo[1].Bar) != 1 || v.Foo[1].Bar[0] != 42 ||
|
|
|
|
|
len(v.Foo[2].Bar) != 1 || v.Foo[2].Bar[0] != 84 {
|
2025-09-03 22:12:13 +02:00
|
|
|
t.Fatal(notation.Sprintw(v))
|
2025-08-31 00:40:47 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("list receiver", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
var l []struct{ Foo int }
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&l,
|
|
|
|
|
bind.NamedValue("foo", 21),
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("foo", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || len(l) != 3 || l[0].Foo != 21 || l[1].Foo != 42 || l[2].Foo != 84 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("list short and cannot be set", func(t *testing.T) {
|
|
|
|
|
type (
|
2025-08-31 01:23:21 +02:00
|
|
|
s0 struct{ Bar int }
|
|
|
|
|
s1 struct{ Foo []s0 }
|
2025-08-31 00:40:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
v := s1{[]s0{{1}, {2}}}
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
v,
|
|
|
|
|
bind.NamedValue("foo-bar", 21),
|
|
|
|
|
bind.NamedValue("foo-bar", 42),
|
|
|
|
|
bind.NamedValue("foo-bar", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 3 || len(v.Foo) != 2 || v.Foo[0].Bar != 1 || v.Foo[1].Bar != 2 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("list short and gets reset", func(t *testing.T) {
|
|
|
|
|
type (
|
2025-08-31 01:23:21 +02:00
|
|
|
s0 struct{ Bar int }
|
|
|
|
|
s1 struct{ Foo []s0 }
|
2025-08-31 00:40:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
v := s1{[]s0{{1}, {2}}}
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo-bar", 21),
|
|
|
|
|
bind.NamedValue("foo-bar", 42),
|
|
|
|
|
bind.NamedValue("foo-bar", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || len(v.Foo) != 3 || v.Foo[0].Bar != 21 || v.Foo[1].Bar != 42 || v.Foo[2].Bar != 84 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("list has invalid type", func(t *testing.T) {
|
|
|
|
|
type (
|
2025-08-31 01:23:21 +02:00
|
|
|
s0 struct{ Bar chan int }
|
|
|
|
|
s1 struct{ Foo []s0 }
|
2025-08-31 00:40:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
v := s1{[]s0{{nil}, {nil}}}
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo-bar", 21),
|
|
|
|
|
bind.NamedValue("foo-bar", 42),
|
|
|
|
|
bind.NamedValue("foo-bar", 84),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 3 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bind scalar map", func(t *testing.T) {
|
|
|
|
|
m := make(map[string]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo-bar", 21), bind.NamedValue("baz-qux", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 2 || m["foo-bar"] != 21 || m["baz-qux"] != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map key conversion", func(t *testing.T) {
|
|
|
|
|
type key string
|
|
|
|
|
m := make(map[key]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 1 || m["foo"] != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map pointer key", func(t *testing.T) {
|
|
|
|
|
m := make(map[*string]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for key, value := range m {
|
|
|
|
|
if *key != "foo" || value != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map list value", func(t *testing.T) {
|
|
|
|
|
m := make(map[string][]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 1 || !slices.Equal(m["foo"], []int{21, 42, 84}) {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(m))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map pointer value", func(t *testing.T) {
|
|
|
|
|
m := make(map[string]*int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for key, value := range m {
|
|
|
|
|
if key != "foo" || *value != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map list pointer value", func(t *testing.T) {
|
|
|
|
|
m := make(map[string]*[]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(m) != 1 || !slices.Equal(*m["foo"], []int{21, 42, 84}) {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("allocate scalar map", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo map[string]int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(v.Foo) != 1 || v.Foo["bar"] != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map addressing via path", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo map[string]int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.ValueByPath([]string{"Foo", "Bar"}, 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || len(v.Foo) != 1 || v.Foo["Bar"] != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map and too long field path", func(t *testing.T) {
|
|
|
|
|
m := make(map[string]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.ValueByPath([]string{"foo", "bar"}, 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map cannot be set", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo map[string]int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar map with wrong value type", func(t *testing.T) {
|
|
|
|
|
m := make(map[string]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(m, bind.NamedValue("foo", "bar"))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("struct fields", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar struct{ Baz string }
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("bar-baz", "qux"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || v.Foo != 42 || v.Bar.Baz != "qux" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("non-existing field", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar struct{ Baz string }
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("bar-qux", "qux"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 1 || u[0].Name() != "bar-qux" || v.Foo != 42 || v.Bar.Baz != "" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("too many fields", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar struct{ Baz string }
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("bar-baz", "qux"),
|
|
|
|
|
bind.NamedValue("bar-baz", "quux"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 2 || u[0].Name() != "bar-baz" || u[1].Name() != "bar-baz" || v.Foo != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("pointer fields", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo *int
|
|
|
|
|
Bar *struct{ Baz *string }
|
|
|
|
|
Qux *[]struct{ Quux string }
|
|
|
|
|
}
|
2025-08-31 17:11:09 +02:00
|
|
|
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("bar-baz", "qux"),
|
|
|
|
|
bind.NamedValue("qux-quux", "corge"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || *v.Foo != 42 || *v.Bar.Baz != "qux" || len(*v.Qux) != 1 || (*v.Qux)[0].Quux != "corge" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-09-03 22:46:09 +02:00
|
|
|
t.Run("pointer field with invalid value", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
type s struct{ Foo *struct{ Bar int } }
|
2025-09-03 22:46:09 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-09-03 22:46:09 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo-bar", "baz"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-08-31 00:40:47 +02:00
|
|
|
t.Run("unsupported pointer fields", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo *int
|
|
|
|
|
Bar *chan int
|
|
|
|
|
}
|
2025-08-31 17:11:09 +02:00
|
|
|
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.NamedValue("foo", 42),
|
|
|
|
|
bind.NamedValue("bar", "qux"),
|
|
|
|
|
bind.NamedValue("bar-baz", "corge"),
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-31 17:11:09 +02:00
|
|
|
sortFields(u)
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 2 || u[0].Name() != "bar" || u[1].Name() != "bar-baz" || *v.Foo != 42 {
|
2025-08-31 17:11:09 +02:00
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
2025-08-31 00:40:47 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("struct fields by path", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar struct{ Baz string }
|
|
|
|
|
}
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(
|
2025-08-31 00:40:47 +02:00
|
|
|
&v,
|
|
|
|
|
bind.ValueByPath([]string{"Foo"}, 42),
|
|
|
|
|
bind.ValueByPath([]string{"Bar", "Baz"}, "qux"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(u) != 0 || v.Foo != 42 || v.Bar.Baz != "qux" {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("cannot set field", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo int }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(v, bind.NamedValue("foo", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("struct with anonymous field", func(t *testing.T) {
|
|
|
|
|
type (
|
2025-08-31 01:23:21 +02:00
|
|
|
s0 struct{ Foo int }
|
|
|
|
|
s1 struct{ s0 }
|
2025-08-31 00:40:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var v s1
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 0 || v.Foo != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("receiver cannot be set", func(t *testing.T) {
|
2025-08-31 01:23:21 +02:00
|
|
|
type s struct{ Foo *struct{ Bar int } }
|
2025-08-31 00:40:47 +02:00
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("receiver not supported", func(t *testing.T) {
|
|
|
|
|
v := make(chan int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 00:40:47 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-31 17:11:09 +02:00
|
|
|
|
|
|
|
|
t.Run("empty receiver", func(t *testing.T) {
|
|
|
|
|
var v any
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo", 42))
|
2025-08-31 17:11:09 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("nil value", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo any }
|
|
|
|
|
v := s{42}
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo", nil))
|
2025-08-31 17:11:09 +02:00
|
|
|
if len(u) != 0 || v.Foo != nil {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("nil receiver", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(nil, bind.NamedValue("foo", nil))
|
2025-08-31 17:11:09 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("ambigously named fields", func(t *testing.T) {
|
|
|
|
|
type s struct {
|
|
|
|
|
FooBar int
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var v s
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo-bar", 42))
|
2025-08-31 17:11:09 +02:00
|
|
|
if len(u) != 0 || v.FooBar != 42 || v.Foo.Bar != 42 {
|
|
|
|
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 22:46:09 +02:00
|
|
|
|
|
|
|
|
t.Run("scalar map with invalid field type", func(t *testing.T) {
|
|
|
|
|
v := make(map[string]int)
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(v, bind.NamedValue("foo", "bar"))
|
2025-09-03 22:46:09 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-09-03 23:52:33 +02:00
|
|
|
|
|
|
|
|
t.Run("unpack interfaces", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
type s struct{ Foo time.Duration }
|
2025-09-03 23:52:33 +02:00
|
|
|
var v s
|
|
|
|
|
var str fmt.Stringer
|
|
|
|
|
str = time.Second
|
2025-09-04 00:33:05 +02:00
|
|
|
u := bind.Bind(&v, bind.NamedValue("foo", &str))
|
2025-09-03 23:52:33 +02:00
|
|
|
if len(u) != 0 || v.Foo != time.Second {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-31 00:40:47 +02:00
|
|
|
})
|
2025-08-31 01:49:53 +02:00
|
|
|
|
|
|
|
|
t.Run("bind fields create", func(t *testing.T) {
|
|
|
|
|
t.Run("circular type", func(t *testing.T) {
|
|
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar *s
|
|
|
|
|
}
|
2025-09-04 00:33:05 +02:00
|
|
|
_, u := bind.CreateAndBind[s](bind.NamedValue("foo", 42))
|
2025-08-31 01:49:53 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("type does not accept fields", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
_, u := bind.CreateAndBind[[]int](bind.NamedValue("foo", 42))
|
2025-08-31 01:49:53 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("zero value when no fields were bound", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo int }
|
2025-09-04 00:33:05 +02:00
|
|
|
v, u := bind.CreateAndBind[*s](bind.NamedValue("bar", 42))
|
2025-08-31 01:49:53 +02:00
|
|
|
if len(u) != 1 || v != nil {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("create receiver", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo int }
|
2025-09-04 00:33:05 +02:00
|
|
|
v, u := bind.CreateAndBind[*s](bind.NamedValue("foo", 42))
|
2025-08-31 01:49:53 +02:00
|
|
|
if len(u) != 0 || v == nil || v.Foo != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-31 17:11:09 +02:00
|
|
|
|
|
|
|
|
t.Run("any type", func(t *testing.T) {
|
2025-09-04 00:33:05 +02:00
|
|
|
_, u := bind.CreateAndBind[any](bind.NamedValue("foo", 42))
|
2025-08-31 17:11:09 +02:00
|
|
|
if len(u) != 1 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-31 01:49:53 +02:00
|
|
|
})
|
2025-09-04 00:33:05 +02:00
|
|
|
|
|
|
|
|
t.Run("bind fields create with reflection type", func(t *testing.T) {
|
|
|
|
|
type s struct{ Foo int }
|
|
|
|
|
typ := reflect.TypeFor[s]()
|
|
|
|
|
v, u := bind.CreateAndBindFor(typ, bind.NamedValue("foo", 42))
|
|
|
|
|
if len(u) != 0 || v.Interface().(s).Foo != 42 {
|
|
|
|
|
t.Fatal()
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-28 05:04:06 +02:00
|
|
|
}
|