From 8683d8ba4074f9325af4892aaa1bb941e6274238 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Thu, 4 Sep 2025 00:33:05 +0200 Subject: [PATCH] refactor/rename --- field.go | 35 ++++++++++--- field_test.go | 137 +++++++++++++++++++++++++++++++------------------ lib.go | 38 +++++++------- lib_test.go | 4 +- scalar.go | 22 ++++---- scalar_test.go | 30 +++++++---- type_test.go | 27 +++++++++- 7 files changed, 189 insertions(+), 104 deletions(-) diff --git a/field.go b/field.go index 507ed4e..0577094 100644 --- a/field.go +++ b/field.go @@ -427,6 +427,18 @@ func fieldsReflect[T any]() []Field { return fields(t) } +func fieldsOf(t reflect.Type) []Field { + if t == nil { + return nil + } + + if hasCircularType(t) { + return nil + } + + return fields(t) +} + func fieldValuesReflect(structure any) []Field { v := reflect.ValueOf(structure) if hasCircularReference(v) { @@ -499,22 +511,18 @@ func bindFieldsReflect(structure any, values []Field) []Field { return bindFields(receiver, values) } -func bindFieldsCreateReflect[T any](values []Field) (T, []Field) { - t := reflect.TypeFor[T]() +func bindFieldsCreate(t reflect.Type, values []Field) (reflect.Value, []Field) { if hasCircularType(t) { - var r T - return r, values + return reflect.Zero(t), values } if !acceptsFields(t) { - var r T - return r, values + return reflect.Zero(t), values } receiver, ok := allocate(t, 1) if !ok { - var r T - return r, values + return reflect.Zero(t), values } unmatched := bindFields(receiver, values) @@ -522,5 +530,16 @@ func bindFieldsCreateReflect[T any](values []Field) (T, []Field) { receiver = reflect.Zero(t) } + return receiver, unmatched +} + +func bindFieldsCreateReflect[T any](values []Field) (T, []Field) { + t := reflect.TypeFor[T]() + receiver, unmatched := bindFieldsCreate(t, values) + if len(unmatched) == len(values) { + var t T + return t, unmatched + } + return receiver.Interface().(T), unmatched } diff --git a/field_test.go b/field_test.go index 5415f40..68c74d2 100644 --- a/field_test.go +++ b/field_test.go @@ -3,12 +3,13 @@ package bind_test import ( "code.squareroundforest.org/arpio/bind" "code.squareroundforest.org/arpio/notation" + "fmt" + "reflect" "slices" "sort" "strings" "testing" "time" - "fmt" ) func TestField(t *testing.T) { @@ -132,7 +133,7 @@ func TestField(t *testing.T) { }) t.Run("list of struct", func(t *testing.T) { - type s struct{Foo []struct{Bar int}} + type s struct{ Foo []struct{ Bar int } } f := bind.Fields[s]() if len(f) != 1 || f[0].Name() != "foo-bar" || !f[0].List() { t.Fatal() @@ -156,7 +157,7 @@ func TestField(t *testing.T) { t.Fatal(notation.Sprint(f)) } - check := map[string]bind.FieldType { + check := map[string]bind.FieldType{ "b": bind.Bool, "i": bind.Int, "u": bind.Uint, @@ -173,6 +174,31 @@ func TestField(t *testing.T) { } } }) + + 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() + } + }) + }) }) t.Run("field values", func(t *testing.T) { @@ -405,7 +431,7 @@ func TestField(t *testing.T) { }) t.Run("list of struct", func(t *testing.T) { - type s struct{Foo []struct{Bar int}} + type s struct{ Foo []struct{ Bar int } } var v s f := bind.FieldValues(v) if len(f) != 1 || f[0].Name() != "foo-bar" || !f[0].List() { @@ -428,7 +454,7 @@ func TestField(t *testing.T) { Bar int } var v s - if len(bind.BindFields(&v, bind.NamedValue("bar", 42))) != 1 { + if len(bind.Bind(&v, bind.NamedValue("bar", 42))) != 1 { t.Fatal() } }) @@ -437,7 +463,7 @@ func TestField(t *testing.T) { type s struct{ Foo int } type p *p var v p - if len(bind.BindFields(&v, bind.NamedValue("foo", 42))) != 1 { + if len(bind.Bind(&v, bind.NamedValue("foo", 42))) != 1 { t.Fatal() } }) @@ -445,7 +471,7 @@ func TestField(t *testing.T) { t.Run("set by name", func(t *testing.T) { type s struct{ FooBar int } var v s - u := bind.BindFields(&v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(&v, bind.NamedValue("foo-bar", 42)) if len(u) != 0 || v.FooBar != 42 { t.Fatal(notation.Sprint(u), notation.Sprint(v)) } @@ -454,7 +480,7 @@ func TestField(t *testing.T) { t.Run("set by path", func(t *testing.T) { type s struct{ FooBar int } var v s - u := bind.BindFields(&v, bind.ValueByPath([]string{"FooBar"}, 42)) + u := bind.Bind(&v, bind.ValueByPath([]string{"FooBar"}, 42)) if len(u) != 0 || v.FooBar != 42 { t.Fatal(notation.Sprint(u), notation.Sprint(v)) } @@ -466,7 +492,7 @@ func TestField(t *testing.T) { Bar int } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 21), bind.NamedValue("baz", 42), @@ -480,7 +506,7 @@ func TestField(t *testing.T) { t.Run("bind list", func(t *testing.T) { type s struct{ Foo []int } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), @@ -495,7 +521,7 @@ func TestField(t *testing.T) { t.Run("bind list of structs", func(t *testing.T) { type s struct{ Foo []struct{ Bar int } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo-bar", 21), bind.NamedValue("foo-bar", 42), @@ -510,7 +536,7 @@ func TestField(t *testing.T) { t.Run("bind list in list", func(t *testing.T) { type s struct{ Foo []struct{ Bar []int } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo-bar", 21), bind.NamedValue("foo-bar", 42), @@ -527,7 +553,7 @@ func TestField(t *testing.T) { t.Run("list receiver", func(t *testing.T) { var l []struct{ Foo int } - u := bind.BindFields( + u := bind.Bind( &l, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), @@ -546,7 +572,7 @@ func TestField(t *testing.T) { ) v := s1{[]s0{{1}, {2}}} - u := bind.BindFields( + u := bind.Bind( v, bind.NamedValue("foo-bar", 21), bind.NamedValue("foo-bar", 42), @@ -565,7 +591,7 @@ func TestField(t *testing.T) { ) v := s1{[]s0{{1}, {2}}} - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo-bar", 21), bind.NamedValue("foo-bar", 42), @@ -584,7 +610,7 @@ func TestField(t *testing.T) { ) v := s1{[]s0{{nil}, {nil}}} - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo-bar", 21), bind.NamedValue("foo-bar", 42), @@ -598,7 +624,7 @@ func TestField(t *testing.T) { t.Run("bind scalar map", func(t *testing.T) { m := make(map[string]int) - u := bind.BindFields(m, bind.NamedValue("foo-bar", 21), bind.NamedValue("baz-qux", 42)) + u := bind.Bind(m, bind.NamedValue("foo-bar", 21), bind.NamedValue("baz-qux", 42)) if len(u) != 0 || len(m) != 2 || m["foo-bar"] != 21 || m["baz-qux"] != 42 { t.Fatal() } @@ -607,7 +633,7 @@ func TestField(t *testing.T) { t.Run("scalar map key conversion", func(t *testing.T) { type key string m := make(map[key]int) - u := bind.BindFields(m, bind.NamedValue("foo", 42)) + u := bind.Bind(m, bind.NamedValue("foo", 42)) if len(u) != 0 || len(m) != 1 || m["foo"] != 42 { t.Fatal() } @@ -615,7 +641,7 @@ func TestField(t *testing.T) { t.Run("scalar map pointer key", func(t *testing.T) { m := make(map[*string]int) - u := bind.BindFields(m, bind.NamedValue("foo", 42)) + u := bind.Bind(m, bind.NamedValue("foo", 42)) if len(u) != 0 || len(m) != 1 { t.Fatal() } @@ -629,7 +655,7 @@ func TestField(t *testing.T) { t.Run("scalar map list value", func(t *testing.T) { m := make(map[string][]int) - u := bind.BindFields(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84)) + u := bind.Bind(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84)) if len(u) != 0 || len(m) != 1 || !slices.Equal(m["foo"], []int{21, 42, 84}) { t.Fatal(notation.Sprint(u), notation.Sprint(m)) } @@ -637,7 +663,7 @@ func TestField(t *testing.T) { t.Run("scalar map pointer value", func(t *testing.T) { m := make(map[string]*int) - u := bind.BindFields(m, bind.NamedValue("foo", 42)) + u := bind.Bind(m, bind.NamedValue("foo", 42)) if len(u) != 0 || len(m) != 1 { t.Fatal() } @@ -651,7 +677,7 @@ func TestField(t *testing.T) { t.Run("scalar map list pointer value", func(t *testing.T) { m := make(map[string]*[]int) - u := bind.BindFields(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84)) + u := bind.Bind(m, bind.NamedValue("foo", 21), bind.NamedValue("foo", 42), bind.NamedValue("foo", 84)) if len(u) != 0 || len(m) != 1 || !slices.Equal(*m["foo"], []int{21, 42, 84}) { t.Fatal() } @@ -660,7 +686,7 @@ func TestField(t *testing.T) { t.Run("allocate scalar map", func(t *testing.T) { type s struct{ Foo map[string]int } var v s - u := bind.BindFields(&v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(&v, bind.NamedValue("foo-bar", 42)) if len(u) != 0 || len(v.Foo) != 1 || v.Foo["bar"] != 42 { t.Fatal() } @@ -669,7 +695,7 @@ func TestField(t *testing.T) { t.Run("scalar map addressing via path", func(t *testing.T) { type s struct{ Foo map[string]int } var v s - u := bind.BindFields(&v, bind.ValueByPath([]string{"Foo", "Bar"}, 42)) + u := bind.Bind(&v, bind.ValueByPath([]string{"Foo", "Bar"}, 42)) if len(u) != 0 || len(v.Foo) != 1 || v.Foo["Bar"] != 42 { t.Fatal() } @@ -677,7 +703,7 @@ func TestField(t *testing.T) { t.Run("scalar map and too long field path", func(t *testing.T) { m := make(map[string]int) - u := bind.BindFields(m, bind.ValueByPath([]string{"foo", "bar"}, 42)) + u := bind.Bind(m, bind.ValueByPath([]string{"foo", "bar"}, 42)) if len(u) != 1 { t.Fatal() } @@ -686,7 +712,7 @@ func TestField(t *testing.T) { t.Run("scalar map cannot be set", func(t *testing.T) { type s struct{ Foo map[string]int } var v s - u := bind.BindFields(v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(v, bind.NamedValue("foo-bar", 42)) if len(u) != 1 { t.Fatal() } @@ -694,7 +720,7 @@ func TestField(t *testing.T) { t.Run("scalar map with wrong value type", func(t *testing.T) { m := make(map[string]int) - u := bind.BindFields(m, bind.NamedValue("foo", "bar")) + u := bind.Bind(m, bind.NamedValue("foo", "bar")) if len(u) != 1 { t.Fatal() } @@ -706,7 +732,7 @@ func TestField(t *testing.T) { Bar struct{ Baz string } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 42), bind.NamedValue("bar-baz", "qux"), @@ -723,7 +749,7 @@ func TestField(t *testing.T) { Bar struct{ Baz string } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 42), bind.NamedValue("bar-qux", "qux"), @@ -740,7 +766,7 @@ func TestField(t *testing.T) { Bar struct{ Baz string } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 42), bind.NamedValue("bar-baz", "qux"), @@ -760,7 +786,7 @@ func TestField(t *testing.T) { } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 42), bind.NamedValue("bar-baz", "qux"), @@ -773,9 +799,9 @@ func TestField(t *testing.T) { }) t.Run("pointer field with invalid value", func(t *testing.T) { - type s struct {Foo *struct{ Bar int }} + type s struct{ Foo *struct{ Bar int } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo-bar", "baz"), ) @@ -792,7 +818,7 @@ func TestField(t *testing.T) { } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.NamedValue("foo", 42), bind.NamedValue("bar", "qux"), @@ -811,7 +837,7 @@ func TestField(t *testing.T) { Bar struct{ Baz string } } var v s - u := bind.BindFields( + u := bind.Bind( &v, bind.ValueByPath([]string{"Foo"}, 42), bind.ValueByPath([]string{"Bar", "Baz"}, "qux"), @@ -825,7 +851,7 @@ func TestField(t *testing.T) { t.Run("cannot set field", func(t *testing.T) { type s struct{ Foo int } var v s - u := bind.BindFields(v, bind.NamedValue("foo", 42)) + u := bind.Bind(v, bind.NamedValue("foo", 42)) if len(u) != 1 { t.Fatal() } @@ -838,7 +864,7 @@ func TestField(t *testing.T) { ) var v s1 - u := bind.BindFields(&v, bind.NamedValue("foo", 42)) + u := bind.Bind(&v, bind.NamedValue("foo", 42)) if len(u) != 0 || v.Foo != 42 { t.Fatal() } @@ -847,7 +873,7 @@ func TestField(t *testing.T) { t.Run("receiver cannot be set", func(t *testing.T) { type s struct{ Foo *struct{ Bar int } } var v s - u := bind.BindFields(v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(v, bind.NamedValue("foo-bar", 42)) if len(u) != 1 { t.Fatal() } @@ -855,7 +881,7 @@ func TestField(t *testing.T) { t.Run("receiver not supported", func(t *testing.T) { v := make(chan int) - u := bind.BindFields(&v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(&v, bind.NamedValue("foo-bar", 42)) if len(u) != 1 { t.Fatal() } @@ -863,7 +889,7 @@ func TestField(t *testing.T) { t.Run("empty receiver", func(t *testing.T) { var v any - u := bind.BindFields(&v, bind.NamedValue("foo", 42)) + u := bind.Bind(&v, bind.NamedValue("foo", 42)) if len(u) != 1 { t.Fatal() } @@ -872,14 +898,14 @@ func TestField(t *testing.T) { t.Run("nil value", func(t *testing.T) { type s struct{ Foo any } v := s{42} - u := bind.BindFields(&v, bind.NamedValue("foo", nil)) + u := bind.Bind(&v, bind.NamedValue("foo", nil)) if len(u) != 0 || v.Foo != nil { t.Fatal() } }) t.Run("nil receiver", func(t *testing.T) { - u := bind.BindFields(nil, bind.NamedValue("foo", nil)) + u := bind.Bind(nil, bind.NamedValue("foo", nil)) if len(u) != 1 { t.Fatal() } @@ -892,7 +918,7 @@ func TestField(t *testing.T) { } var v s - u := bind.BindFields(&v, bind.NamedValue("foo-bar", 42)) + u := bind.Bind(&v, bind.NamedValue("foo-bar", 42)) if len(u) != 0 || v.FooBar != 42 || v.Foo.Bar != 42 { t.Fatal(notation.Sprint(u), notation.Sprint(v)) } @@ -900,18 +926,18 @@ func TestField(t *testing.T) { t.Run("scalar map with invalid field type", func(t *testing.T) { v := make(map[string]int) - u := bind.BindFields(v, bind.NamedValue("foo", "bar")) + u := bind.Bind(v, bind.NamedValue("foo", "bar")) if len(u) != 1 { t.Fatal() } }) t.Run("unpack interfaces", func(t *testing.T) { - type s struct{Foo time.Duration} + type s struct{ Foo time.Duration } var v s var str fmt.Stringer str = time.Second - u := bind.BindFields(&v, bind.NamedValue("foo", &str)) + u := bind.Bind(&v, bind.NamedValue("foo", &str)) if len(u) != 0 || v.Foo != time.Second { t.Fatal() } @@ -924,14 +950,14 @@ func TestField(t *testing.T) { Foo int Bar *s } - _, u := bind.BindFieldsCreate[s](bind.NamedValue("foo", 42)) + _, u := bind.CreateAndBind[s](bind.NamedValue("foo", 42)) if len(u) != 1 { t.Fatal() } }) t.Run("type does not accept fields", func(t *testing.T) { - _, u := bind.BindFieldsCreate[[]int](bind.NamedValue("foo", 42)) + _, u := bind.CreateAndBind[[]int](bind.NamedValue("foo", 42)) if len(u) != 1 { t.Fatal() } @@ -939,7 +965,7 @@ func TestField(t *testing.T) { t.Run("zero value when no fields were bound", func(t *testing.T) { type s struct{ Foo int } - v, u := bind.BindFieldsCreate[*s](bind.NamedValue("bar", 42)) + v, u := bind.CreateAndBind[*s](bind.NamedValue("bar", 42)) if len(u) != 1 || v != nil { t.Fatal() } @@ -947,17 +973,26 @@ func TestField(t *testing.T) { t.Run("create receiver", func(t *testing.T) { type s struct{ Foo int } - v, u := bind.BindFieldsCreate[*s](bind.NamedValue("foo", 42)) + v, u := bind.CreateAndBind[*s](bind.NamedValue("foo", 42)) if len(u) != 0 || v == nil || v.Foo != 42 { t.Fatal() } }) t.Run("any type", func(t *testing.T) { - _, u := bind.BindFieldsCreate[any](bind.NamedValue("foo", 42)) + _, u := bind.CreateAndBind[any](bind.NamedValue("foo", 42)) if len(u) != 1 { t.Fatal() } }) }) + + 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() + } + }) } diff --git a/lib.go b/lib.go index f31f730..0bc665a 100644 --- a/lib.go +++ b/lib.go @@ -49,13 +49,13 @@ func BindScalar(receiver any, value ...any) bool { } // BindScalarCreate is like BindScalar, but it allocates the receiver from the type T. -func BindScalarCreate[T any](value ...any) (T, bool) { +func CreateAndBindScalar[T any](value ...any) (T, bool) { return bindScalarCreateReflect[T](value) } // BindScalarCreate is like BindScalar, but it allocates the receiver from the type t. -func BindScalarCreateWith(t reflect.Type, value ...any) (reflect.Value, bool) { - return reflect.Value{}, false +func CreateAndBindScalarFor(t reflect.Type, value ...any) (reflect.Value, bool) { + return bindScalarCreate(t, value) } // ValueByPath defines a field for input to BindFields or BindFieldsCreate. It defines the field by its exact @@ -119,7 +119,7 @@ func Fields[T any]() []Field { // Fields of is like Fields but uses type t as the input. func FieldsOf(t reflect.Type) []Field { - return nil + return fieldsOf(t) } // FieldValues returns the fields of a structure value recursively. It traverses through pointers, slices and @@ -128,21 +128,21 @@ func FieldValues(structure any) []Field { return fieldValuesReflect(structure) } -// BindFields sets structure fields recursively. It traverses through poitners, slices and interfaces. It -// returns the values for which it is not possible to find a compatible matching field. It supports maps that -// have string keys and scalar values. -func BindFields(structure any, values ...Field) []Field { - return bindFieldsReflect(structure, values) +// Bind sets structure fields recursively. It traverses through poitners, slices and interfaces. It returns the +// values for which it is not possible to find a compatible matching field. It supports maps that have string +// keys and scalar values. +func Bind(structure any, value ...Field) []Field { + return bindFieldsReflect(structure, value) } -// BindFieldsCreate is like BindFields, but it allocates the receiver from type T. -func BindFieldsCreate[T any](values ...Field) (T, []Field) { - return bindFieldsCreateReflect[T](values) +// CreateAndBind is like Bind, but it allocates the receiver from type T. +func CreateAndBind[T any](value ...Field) (T, []Field) { + return bindFieldsCreateReflect[T](value) } -// BindFieldsCreate is like BindFields, but it allocates the receiver from type t. -func BindFieldsCreateWith(t reflect.Type, values ...Field) (reflect.Value, []Field) { - return reflect.Value{}, nil +// CreateAndBindFor is like Bind, but it allocates the receiver from type t. +func CreateAndBindFor(t reflect.Type, value ...Field) (reflect.Value, []Field) { + return bindFieldsCreate(t, value) } // AcceptsScalar checks if a type can be used with BindScalarCreate or the values of the type with BindScalar. @@ -152,7 +152,7 @@ func AcceptsScalar[T any]() bool { // TypeAcceptsScalar is like AcceptsScalar, but uses type t as input. func TypeAcceptsScalar(t reflect.Type) bool { - return false + return acceptsScalarChecked(t) } // AcceptsFields checks if a type can be used with BindFieldsCreate or the values of the type with BindFields. @@ -162,7 +162,7 @@ func AcceptsFields[T any]() bool { // TypeAcceptsFields is like AcceptsFields, but uses type t as input. func TypeAcceptsFields(t reflect.Type) bool { - return false + return acceptsFieldsChecked(t) } // AcceptsList checks if a type can be used to bind multiple values. @@ -172,7 +172,7 @@ func AcceptsList[T any]() bool { // TypeAcceptsList is like AcceptsList, but uses type t as input. func TypeAcceptsList(t reflect.Type) bool { - return false + return acceptsListChecked(t) } // Bindable is the same as AcceptsScalar[T]() || AcceptsFields[T](). @@ -182,5 +182,5 @@ func Bindable[T any]() bool { // BindableType is like Bindable, but uses type t as input. func BindableType(t reflect.Type) bool { - return false + return bindableChecked(t) } diff --git a/lib_test.go b/lib_test.go index c03e232..e226209 100644 --- a/lib_test.go +++ b/lib_test.go @@ -1,15 +1,15 @@ package bind_test import ( - "testing" "code.squareroundforest.org/arpio/bind" + "testing" ) func TestLib(t *testing.T) { t.Run("field", func(t *testing.T) { t.Run("type", func(t *testing.T) { t.Run("not from input", func(t *testing.T) { - v := struct{Foo int}{42} + v := struct{ Foo int }{42} f := bind.FieldValues(v) if len(f) != 1 || f[0].Type() != bind.Int { t.Fatal() diff --git a/scalar.go b/scalar.go index 362d4ec..503fac7 100644 --- a/scalar.go +++ b/scalar.go @@ -56,6 +56,16 @@ func bindScalarCreate(t reflect.Type, values []any) (reflect.Value, bool) { return reflect.Zero(t), false } + if hasCircularType(t) { + return reflect.Zero(t), false + } + + for _, vi := range values { + if hasCircularReference(reflect.ValueOf(vi)) { + return reflect.Zero(t), false + } + } + if !acceptsScalar(t) { return reflect.Zero(t), false } @@ -89,18 +99,6 @@ func bindScalarReflect(receiver any, values []any) bool { func bindScalarCreateReflect[T any](values []any) (T, bool) { t := reflect.TypeFor[T]() - if hasCircularType(t) { - var tt T - return tt, false - } - - for _, vi := range values { - if hasCircularReference(reflect.ValueOf(vi)) { - var tt T - return tt, false - } - } - v, ok := bindScalarCreate(t, values) if !ok { var tt T diff --git a/scalar_test.go b/scalar_test.go index 0f06e58..04a7fcc 100644 --- a/scalar_test.go +++ b/scalar_test.go @@ -2,6 +2,7 @@ package bind_test import ( "code.squareroundforest.org/arpio/bind" + "reflect" "slices" "testing" ) @@ -181,62 +182,62 @@ func TestScalar(t *testing.T) { t.Run("bind scalar with create", func(t *testing.T) { t.Run("no value", func(t *testing.T) { - if _, ok := bind.BindScalarCreate[int](); ok { + if _, ok := bind.CreateAndBindScalar[int](); ok { t.Fatal() } }) t.Run("does not accept scalar", func(t *testing.T) { - if _, ok := bind.BindScalarCreate[struct{ Foo int }]("42"); ok { + if _, ok := bind.CreateAndBindScalar[struct{ Foo int }]("42"); ok { t.Fatal() } }) t.Run("empty interface", func(t *testing.T) { - if v, ok := bind.BindScalarCreate[any]("42"); !ok || v != "42" { + if v, ok := bind.CreateAndBindScalar[any]("42"); !ok || v != "42" { t.Fatal() } }) t.Run("scalar", func(t *testing.T) { - if v, ok := bind.BindScalarCreate[int]("42"); !ok || v != 42 { + if v, ok := bind.CreateAndBindScalar[int]("42"); !ok || v != 42 { t.Fatal() } }) t.Run("slice", func(t *testing.T) { - if v, ok := bind.BindScalarCreate[[]int]("21", "42", "84"); !ok || !slices.Equal(v, []int{21, 42, 84}) { + if v, ok := bind.CreateAndBindScalar[[]int]("21", "42", "84"); !ok || !slices.Equal(v, []int{21, 42, 84}) { t.Fatal() } }) t.Run("slice with non-scalar type", func(t *testing.T) { - if _, ok := bind.BindScalarCreate[[]func(int)]("21", "42", "84"); ok { + if _, ok := bind.CreateAndBindScalar[[]func(int)]("21", "42", "84"); ok { t.Fatal() } }) t.Run("pointer to non-scalar", func(t *testing.T) { - if _, ok := bind.BindScalarCreate[*func()]("42"); ok { + if _, ok := bind.CreateAndBindScalar[*func()]("42"); ok { t.Fatal() } }) t.Run("pointer", func(t *testing.T) { - if v, ok := bind.BindScalarCreate[*int]("42"); !ok || *v != 42 { + if v, ok := bind.CreateAndBindScalar[*int]("42"); !ok || *v != 42 { t.Fatal() } }) t.Run("unscannable", func(t *testing.T) { - if _, ok := bind.BindScalarCreate[int]("foo"); ok { + if _, ok := bind.CreateAndBindScalar[int]("foo"); ok { t.Fatal() } }) t.Run("receiver has circular type", func(t *testing.T) { type s []s - if _, ok := bind.BindScalarCreate[s]("foo"); ok { + if _, ok := bind.CreateAndBindScalar[s]("foo"); ok { t.Fatal() } }) @@ -245,9 +246,16 @@ func TestScalar(t *testing.T) { var v any p := &v *p = p - if _, ok := bind.BindScalarCreate[any](p); ok { + if _, ok := bind.CreateAndBindScalar[any](p); ok { t.Fatal() } }) }) + + t.Run("bind with reflection type", func(t *testing.T) { + v, ok := bind.CreateAndBindScalarFor(reflect.TypeFor[int](), 42) + if !ok || v.Interface() != 42 { + t.Fatal() + } + }) } diff --git a/type_test.go b/type_test.go index f40c648..f1bd886 100644 --- a/type_test.go +++ b/type_test.go @@ -2,6 +2,7 @@ package bind_test import ( "code.squareroundforest.org/arpio/bind" + "reflect" "testing" ) @@ -274,6 +275,12 @@ func TestTypeChecks(t *testing.T) { t.Fatal() } }) + + t.Run("with reflection type", func(t *testing.T) { + if !bind.BindableType(reflect.TypeFor[int]()) { + t.Fatal() + } + }) }) t.Run("circular type", func(t *testing.T) { @@ -378,11 +385,17 @@ func TestTypeChecks(t *testing.T) { t.Fatal() } }) + + t.Run("with reflection type", func(t *testing.T) { + if !bind.TypeAcceptsScalar(reflect.TypeFor[int]()) { + t.Fatal() + } + }) }) t.Run("fields", func(t *testing.T) { t.Run("yes", func(t *testing.T) { - type s struct{Foo int} + type s struct{ Foo int } if !bind.AcceptsFields[s]() { t.Fatal() } @@ -400,6 +413,12 @@ func TestTypeChecks(t *testing.T) { t.Fatal() } }) + + t.Run("with reflection type", func(t *testing.T) { + if !bind.TypeAcceptsFields(reflect.TypeFor[struct{ Foo int }]()) { + t.Fatal() + } + }) }) t.Run("list", func(t *testing.T) { @@ -421,6 +440,12 @@ func TestTypeChecks(t *testing.T) { t.Fatal() } }) + + t.Run("with reflection type", func(t *testing.T) { + if !bind.TypeAcceptsList(reflect.TypeFor[[]int]()) { + t.Fatal() + } + }) }) }) }