field binding
This commit is contained in:
parent
613cbf94f0
commit
b0ff605b25
392
field.go
392
field.go
@ -5,12 +5,48 @@ import (
|
|||||||
"github.com/iancoleman/strcase"
|
"github.com/iancoleman/strcase"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func pathString(f Field) string {
|
||||||
|
return strings.Join(f.path, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameFromPath(p []string) string {
|
||||||
|
var pp []string
|
||||||
|
for _, pi := range p {
|
||||||
|
pp = append(pp, strcase.ToKebab(pi))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(pp, "-")
|
||||||
|
}
|
||||||
|
|
||||||
func exported(name string) bool {
|
func exported(name string) bool {
|
||||||
return unicode.IsUpper([]rune(name)[0])
|
return unicode.IsUpper([]rune(name)[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterFields(predicate func(Field) bool, f []Field) ([]Field, []Field) {
|
||||||
|
var yes, no []Field
|
||||||
|
for _, fi := range f {
|
||||||
|
if predicate(fi) {
|
||||||
|
yes = append(yes, fi)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
no = append(no, fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return yes, no
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldHasCircRef(f Field) bool {
|
||||||
|
return hasCircularReference(reflect.ValueOf(f.Value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPath(f Field) bool {
|
||||||
|
return len(f.path) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func fields(t reflect.Type) []Field {
|
func fields(t reflect.Type) []Field {
|
||||||
t = unpackType(t, pointer)
|
t = unpackType(t, pointer)
|
||||||
list := t.Kind() == reflect.Slice
|
list := t.Kind() == reflect.Slice
|
||||||
@ -65,62 +101,41 @@ func fields(t reflect.Type) []Field {
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func fieldFromValue(name string, v reflect.Value) Field {
|
func fieldFromValue(v reflect.Value) Field {
|
||||||
var fi Field
|
var fi Field
|
||||||
fi.name = strcase.ToKebab(name)
|
|
||||||
fi.path = []string{name}
|
|
||||||
fi.value = v.Interface()
|
fi.value = v.Interface()
|
||||||
fi.isBool = v.Kind() == reflect.Bool
|
fi.isBool = v.Kind() == reflect.Bool
|
||||||
return fi
|
return fi
|
||||||
}
|
}
|
||||||
|
|
||||||
func scalarMapFields(v reflect.Value) []Field {
|
|
||||||
var f []Field
|
|
||||||
for _, key := range v.MapKeys() {
|
|
||||||
value := v.MapIndex(key)
|
|
||||||
name := key.Interface().(string)
|
|
||||||
if value.Kind() == reflect.Slice {
|
|
||||||
for i := 0; i < value.Len(); i++ {
|
|
||||||
fi := fieldFromValue(name, value.Index(i))
|
|
||||||
fi.free = true
|
|
||||||
f = append(f, fi)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fi := fieldFromValue(name, value)
|
|
||||||
fi.free = true
|
|
||||||
f = append(f, fi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func prependFieldName(name string, f []Field) {
|
func prependFieldName(name string, f []Field) {
|
||||||
for i := range f {
|
for i := range f {
|
||||||
|
if f[i].name == "" {
|
||||||
|
f[i].name = strcase.ToKebab(name)
|
||||||
|
f[i].path = []string{name}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
f[i].name = fmt.Sprintf("%s-%s", strcase.ToKebab(name), f[i].name)
|
f[i].name = fmt.Sprintf("%s-%s", strcase.ToKebab(name), f[i].name)
|
||||||
f[i].path = append([]string{name}, f[i].path...)
|
f[i].path = append([]string{name}, f[i].path...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listFieldValues(fieldName string, l reflect.Value) []Field {
|
func freeFields(f []Field) {
|
||||||
|
for i := range f {
|
||||||
|
f[i].free = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarMapFields(v reflect.Value) []Field {
|
||||||
var f []Field
|
var f []Field
|
||||||
for i := 0; i < l.Len(); i++ {
|
for _, key := range v.MapKeys() {
|
||||||
item := l.Index(i)
|
name := key.Interface().(string)
|
||||||
item = unpackValue(item, pointer|iface|anytype)
|
value := v.MapIndex(key)
|
||||||
switch {
|
fk := fieldValues(value)
|
||||||
case isScalar(item.Type()):
|
prependFieldName(name, fk)
|
||||||
f = append(f, fieldFromValue(fieldName, item))
|
freeFields(fk)
|
||||||
case item.Kind() == reflect.Slice:
|
f = append(f, fk...)
|
||||||
f = append(f, listFieldValues(fieldName, item)...)
|
|
||||||
case isScalarMap(item.Type()):
|
|
||||||
mf := fieldValues(item)
|
|
||||||
prependFieldName(fieldName, mf)
|
|
||||||
f = append(f, mf...)
|
|
||||||
case item.Kind() == reflect.Struct:
|
|
||||||
sf := fieldValues(item)
|
|
||||||
prependFieldName(fieldName, sf)
|
|
||||||
f = append(f, sf...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return f
|
return f
|
||||||
@ -129,6 +144,11 @@ func listFieldValues(fieldName string, l reflect.Value) []Field {
|
|||||||
func fieldValues(v reflect.Value) []Field {
|
func fieldValues(v reflect.Value) []Field {
|
||||||
var f []Field
|
var f []Field
|
||||||
v = unpackValue(v, pointer|anytype|iface)
|
v = unpackValue(v, pointer|anytype|iface)
|
||||||
|
t := v.Type()
|
||||||
|
if isScalar(t) {
|
||||||
|
return []Field{fieldFromValue(v)}
|
||||||
|
}
|
||||||
|
|
||||||
if v.Kind() == reflect.Slice {
|
if v.Kind() == reflect.Slice {
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
f = append(f, fieldValues(v.Index(i))...)
|
f = append(f, fieldValues(v.Index(i))...)
|
||||||
@ -137,7 +157,6 @@ func fieldValues(v reflect.Value) []Field {
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
t := v.Type()
|
|
||||||
if isScalarMap(t) {
|
if isScalarMap(t) {
|
||||||
return scalarMapFields(v)
|
return scalarMapFields(v)
|
||||||
}
|
}
|
||||||
@ -155,30 +174,234 @@ func fieldValues(v reflect.Value) []Field {
|
|||||||
vfi := v.Field(i)
|
vfi := v.Field(i)
|
||||||
vfi = unpackValue(vfi, pointer|iface|anytype)
|
vfi = unpackValue(vfi, pointer|iface|anytype)
|
||||||
switch {
|
switch {
|
||||||
case isScalar(vfi.Type()):
|
case vfi.Kind() == reflect.Struct && tfi.Anonymous:
|
||||||
f = append(f, fieldFromValue(tfi.Name, vfi))
|
ff := fieldValues(vfi)
|
||||||
case vfi.Kind() == reflect.Slice:
|
f = append(f, ff...)
|
||||||
f = append(f, listFieldValues(tfi.Name, vfi)...)
|
default:
|
||||||
case isScalarMap(vfi.Type()):
|
ff := fieldValues(vfi)
|
||||||
mf := fieldValues(vfi)
|
prependFieldName(tfi.Name, ff)
|
||||||
prependFieldName(tfi.Name, mf)
|
f = append(f, ff...)
|
||||||
f = append(f, mf...)
|
|
||||||
case vfi.Kind() == reflect.Struct:
|
|
||||||
sf := fieldValues(vfi)
|
|
||||||
if !tfi.Anonymous {
|
|
||||||
prependFieldName(tfi.Name, sf)
|
|
||||||
}
|
|
||||||
|
|
||||||
f = append(f, sf...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func takeFieldValues(f []Field) []any {
|
||||||
|
var v []any
|
||||||
|
for _, fi := range f {
|
||||||
|
v = append(v, fi.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindScalarField(receiver reflect.Value, values []Field) bool {
|
||||||
|
v := takeFieldValues(values)
|
||||||
|
if !receiver.CanSet() {
|
||||||
|
return bindScalar(receiver, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
rv, ok := allocate(receiver.Type(), len(v))
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok = bindScalar(rv, v); ok {
|
||||||
|
receiver.Set(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindListField(receiver reflect.Value, values []Field) bool {
|
||||||
|
if receiver.Len() < len(values) && !receiver.CanSet() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver.Len() < len(values) {
|
||||||
|
newList, ok := allocate(receiver.Type(), len(values))
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
reflect.Copy(newList, receiver)
|
||||||
|
receiver.Set(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range values {
|
||||||
|
if !bindField(receiver.Index(i), values[i:i+1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindMapField(receiver reflect.Value, values []Field) bool {
|
||||||
|
for _, v := range values {
|
||||||
|
if len(v.path) > 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver.IsZero() && !receiver.CanSet() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var key string
|
||||||
|
fp, nfp := filterFields(hasPath, values)
|
||||||
|
if len(fp) > 0 {
|
||||||
|
key = fp[0].path[0]
|
||||||
|
} else {
|
||||||
|
key = nfp[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
v := takeFieldValues(values)
|
||||||
|
t := receiver.Type()
|
||||||
|
kt := t.Key()
|
||||||
|
vt := t.Elem()
|
||||||
|
kv, ok := bindScalarCreate(kt, []any{key})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
vv, ok := bindScalarCreate(vt, v)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver.IsZero() {
|
||||||
|
rv, ok := allocate(receiver.Type(), 1)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver.Set(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver.SetMapIndex(kv, vv)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimNameAndPath(name string, values []Field) []Field {
|
||||||
|
name = strcase.ToKebab(name)
|
||||||
|
v := make([]Field, len(values))
|
||||||
|
copy(v, values)
|
||||||
|
for i := range v {
|
||||||
|
if len(v[i].path) > 0 {
|
||||||
|
v[i].path = v[i].path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if v[i].name == name {
|
||||||
|
v[i].name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(v[i].name, fmt.Sprintf("%s-", name)) {
|
||||||
|
v[i].name = v[i].name[len(name) + 1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindStructField(receiver reflect.Value, values []Field) bool {
|
||||||
|
var name, pathName string
|
||||||
|
fp, nfp := filterFields(hasPath, values)
|
||||||
|
if len(fp) > 0 {
|
||||||
|
pathName = fp[0].path[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nfp) > 0 {
|
||||||
|
name = nfp[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
t := receiver.Type()
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
sf := t.Field(i)
|
||||||
|
if sf.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if sf.Name == pathName {
|
||||||
|
values = trimNameAndPath(pathName, values)
|
||||||
|
return bindField(receiver.Field(i), values)
|
||||||
|
}
|
||||||
|
|
||||||
|
sfn := strcase.ToKebab(sf.Name)
|
||||||
|
if name == sfn ||
|
||||||
|
strings.HasPrefix(name, fmt.Sprintf("%s-", sfn)) {
|
||||||
|
values = trimNameAndPath(sfn, values)
|
||||||
|
return bindField(receiver.Field(i), values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
sf := t.Field(i)
|
||||||
|
if !sf.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bindField(receiver.Field(i), values) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindField(receiver reflect.Value, values []Field) bool {
|
||||||
|
if values[0].name == "" && len(values[0].path) == 0 {
|
||||||
|
return bindScalarField(receiver, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
listReceiver := unpackValue(receiver, pointer|iface|anytype)
|
||||||
|
if listReceiver.Kind() == reflect.Slice {
|
||||||
|
return bindListField(listReceiver, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldReceiver := unpackValue(receiver, pointer|slice|iface)
|
||||||
|
if isScalarMap(fieldReceiver.Type()) {
|
||||||
|
return bindMapField(fieldReceiver, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldReceiver.Kind() == reflect.Struct {
|
||||||
|
return bindStructField(fieldReceiver, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !receiver.CanSet() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t := receiver.Type()
|
||||||
|
ut := unpackType(t, pointer)
|
||||||
|
if ut.Kind() == reflect.Slice ||
|
||||||
|
isScalarMap(ut) ||
|
||||||
|
ut.Kind() == reflect.Struct {
|
||||||
|
l := 1
|
||||||
|
if ut.Kind() == reflect.Slice {
|
||||||
|
l = len(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
rv, ok := allocate(t, l)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bindField(rv, values) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver.Set(rv)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func fieldsReflect[T any]() []Field {
|
func fieldsReflect[T any]() []Field {
|
||||||
t := reflect.TypeFor[T]()
|
t := reflect.TypeFor[T]()
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,9 +410,58 @@ func fieldsReflect[T any]() []Field {
|
|||||||
|
|
||||||
func fieldValuesReflect(structure any) []Field {
|
func fieldValuesReflect(structure any) []Field {
|
||||||
v := reflect.ValueOf(structure)
|
v := reflect.ValueOf(structure)
|
||||||
if hasCircularReference(nil, v) {
|
if hasCircularReference(v) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fieldValues(v)
|
return fieldValues(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func groupFields(f []Field) [][]Field {
|
||||||
|
withPath, withoutPath := filterFields(hasPath, f)
|
||||||
|
paths := make(map[string][]Field)
|
||||||
|
for _, ff := range withPath {
|
||||||
|
ps := pathString(ff)
|
||||||
|
paths[ps] = append(paths[ps], ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make(map[string][]Field)
|
||||||
|
for _, ff := range withoutPath {
|
||||||
|
names[ff.name] = append(names[ff.name], ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups [][]Field
|
||||||
|
for _, group := range paths {
|
||||||
|
nfp := nameFromPath(group[0].path)
|
||||||
|
group = append(group, names[nfp]...)
|
||||||
|
delete(names, nfp)
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range names {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindFieldsReflect(structure any, values []Field) []Field {
|
||||||
|
receiver := reflect.ValueOf(structure)
|
||||||
|
if hasCircularReference(receiver) {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
if !acceptsFields(receiver.Type()) {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
unmatched, try := filterFields(fieldHasCircRef, values)
|
||||||
|
groups := groupFields(try)
|
||||||
|
for _, g := range groups {
|
||||||
|
if !bindField(receiver, g) {
|
||||||
|
unmatched = append(unmatched, g...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmatched
|
||||||
|
}
|
||||||
|
|||||||
409
field_test.go
409
field_test.go
@ -128,7 +128,7 @@ func TestField(t *testing.T) {
|
|||||||
if len(f) != 2 ||
|
if len(f) != 2 ||
|
||||||
f[0].Name() != "foo" || f[0].Value() != 21 || !f[0].Free() ||
|
f[0].Name() != "foo" || f[0].Value() != 21 || !f[0].Free() ||
|
||||||
f[1].Name() != "bar" || f[1].Value() != 42 || !f[1].Free() {
|
f[1].Name() != "bar" || f[1].Value() != 42 || !f[1].Free() {
|
||||||
t.Fatal(notation.Println(f))
|
t.Fatal(notation.Sprint(f))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -151,14 +151,14 @@ func TestField(t *testing.T) {
|
|||||||
f[1].Name() != "foo" || f[1].Value() != 36 || !f[1].Free() ||
|
f[1].Name() != "foo" || f[1].Value() != 36 || !f[1].Free() ||
|
||||||
f[2].Name() != "bar" || f[2].Value() != 42 || !f[2].Free() ||
|
f[2].Name() != "bar" || f[2].Value() != 42 || !f[2].Free() ||
|
||||||
f[3].Name() != "bar" || f[3].Value() != 72 || !f[3].Free() {
|
f[3].Name() != "bar" || f[3].Value() != 72 || !f[3].Free() {
|
||||||
t.Fatal(notation.Println(f))
|
t.Fatal(notation.Sprint(f))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("not a struct", func(t *testing.T) {
|
t.Run("not a struct", func(t *testing.T) {
|
||||||
v := []int{21, 42, 84}
|
v := []int{21, 42, 84}
|
||||||
f := bind.FieldValues(v)
|
f := bind.FieldValues(v)
|
||||||
if len(f) != 0 {
|
if len(f) != 3 || f[0].Value() != 21 || f[1].Value() != 42 || f[2].Value() != 84 {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -168,10 +168,11 @@ func TestField(t *testing.T) {
|
|||||||
Foo int
|
Foo int
|
||||||
bar string
|
bar string
|
||||||
}
|
}
|
||||||
|
|
||||||
v := s{Foo: 42, bar: "baz"}
|
v := s{Foo: 42, bar: "baz"}
|
||||||
f := bind.FieldValues(v)
|
f := bind.FieldValues(v)
|
||||||
if len(f) != 1 || f[0].Name() != "foo" {
|
if len(f) != 1 || f[0].Name() != "foo" {
|
||||||
t.Fatal()
|
t.Fatal(notation.Sprint(f))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -275,4 +276,404 @@ func TestField(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("bind fields", func(t *testing.T) {
|
||||||
|
t.Run("no circular receiver", func(t *testing.T) {
|
||||||
|
type s struct{Foo *s; Bar int}
|
||||||
|
var v s
|
||||||
|
if len(bind.BindFields(&v, bind.NamedValue("bar", 42))) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no circular valeu", func(t *testing.T) {
|
||||||
|
type s struct{Foo int}
|
||||||
|
type p *p
|
||||||
|
var v p
|
||||||
|
if len(bind.BindFields(&v, bind.NamedValue("foo", 42))) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
if len(u) != 0 || v.FooBar != 42 {
|
||||||
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
if len(u) != 0 || v.FooBar != 42 {
|
||||||
|
t.Fatal(notation.Sprint(u), notation.Sprint(v))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fail to bind", func(t *testing.T) {
|
||||||
|
type s struct{Foo int; Bar int}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo []int}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo []struct{Bar int}}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo []struct{Bar []int}}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("list receiver", func(t *testing.T) {
|
||||||
|
var l []struct{Foo int}
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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 (
|
||||||
|
s0 struct{Bar int}
|
||||||
|
s1 struct{Foo []s0}
|
||||||
|
)
|
||||||
|
|
||||||
|
v := s1{[]s0{{1}, {2}}}
|
||||||
|
u := bind.BindFields(
|
||||||
|
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 (
|
||||||
|
s0 struct{Bar int}
|
||||||
|
s1 struct{Foo []s0}
|
||||||
|
)
|
||||||
|
|
||||||
|
v := s1{[]s0{{1}, {2}}}
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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 (
|
||||||
|
s0 struct{Bar chan int}
|
||||||
|
s1 struct{Foo []s0}
|
||||||
|
)
|
||||||
|
|
||||||
|
v := s1{[]s0{{nil}, {nil}}}
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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)
|
||||||
|
u := bind.BindFields(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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
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)
|
||||||
|
u := bind.BindFields(m, bind.NamedValue("foo", 42))
|
||||||
|
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)
|
||||||
|
u := bind.BindFields(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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scalar map pointer value", func(t *testing.T) {
|
||||||
|
m := make(map[string]*int)
|
||||||
|
u := bind.BindFields(m, bind.NamedValue("foo", 42))
|
||||||
|
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)
|
||||||
|
u := bind.BindFields(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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
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) {
|
||||||
|
type s struct{Foo map[string]int}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(&v, bind.ValueByPath([]string{"Foo", "Bar"}, 42))
|
||||||
|
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)
|
||||||
|
u := bind.BindFields(m, bind.ValueByPath([]string{"foo", "bar"}, 42))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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"))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("struct fields", func(t *testing.T) {
|
||||||
|
type s struct{Foo int; Bar struct { Baz string }}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo int; Bar struct { Baz string }}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo int; Bar struct { Baz string }}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo *int; Bar *struct { Baz *string }; Qux *[]struct{Quux string}}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unsupported pointer fields", func(t *testing.T) {
|
||||||
|
type s struct{Foo *int; Bar *chan int}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&v,
|
||||||
|
bind.NamedValue("foo", 42),
|
||||||
|
bind.NamedValue("bar", "qux"),
|
||||||
|
bind.NamedValue("bar-baz", "corge"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(u) != 2 || u[0].Name() != "bar" || u[1].Name() != "bar-baz" || *v.Foo != 42 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("struct fields by path", func(t *testing.T) {
|
||||||
|
type s struct{Foo int; Bar struct { Baz string }}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(
|
||||||
|
&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) {
|
||||||
|
type s struct{Foo int}
|
||||||
|
var v s
|
||||||
|
u := bind.BindFields(v, bind.NamedValue("foo", 42))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("struct with anonymous field", func(t *testing.T) {
|
||||||
|
type (
|
||||||
|
s0 struct{Foo int}
|
||||||
|
s1 struct{s0}
|
||||||
|
)
|
||||||
|
|
||||||
|
var v s1
|
||||||
|
u := bind.BindFields(&v, bind.NamedValue("foo", 42))
|
||||||
|
if len(u) != 0 || v.Foo != 42 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("receiver not supported", func(t *testing.T) {
|
||||||
|
v := make(chan int)
|
||||||
|
u := bind.BindFields(&v, bind.NamedValue("foo-bar", 42))
|
||||||
|
if len(u) != 1 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
29
lib.go
29
lib.go
@ -5,10 +5,10 @@
|
|||||||
package bind
|
package bind
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
name string
|
|
||||||
path []string
|
path []string
|
||||||
isBool bool
|
name string
|
||||||
list bool
|
list bool
|
||||||
|
isBool bool
|
||||||
free bool
|
free bool
|
||||||
value any
|
value any
|
||||||
}
|
}
|
||||||
@ -22,11 +22,11 @@ func BindScalarCreate[T any](value ...any) (T, bool) {
|
|||||||
return bindScalarCreateReflect[T](value)
|
return bindScalarCreateReflect[T](value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FieldValue(path []string, value any) Field {
|
func ValueByPath(path []string, value any) Field {
|
||||||
return Field{path: path, value: value}
|
return Field{path: path, value: value}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FieldValueByName(name string, value any) Field {
|
func NamedValue(name string, value any) Field {
|
||||||
return Field{name: name, value: value}
|
return Field{name: name, value: value}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,26 +36,37 @@ func (f Field) Path() []string {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Name() string { return f.name }
|
func (f Field) Name() string {
|
||||||
|
if f.name != "" || len(f.path) == 0 {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameFromPath(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
func (f Field) List() bool { return f.list }
|
func (f Field) List() bool { return f.list }
|
||||||
func (f Field) Bool() bool { return f.isBool }
|
func (f Field) Bool() bool { return f.isBool }
|
||||||
func (f Field) Free() bool { return f.free }
|
func (f Field) Free() bool { return f.free }
|
||||||
func (f Field) Value() any { return f.value }
|
func (f Field) Value() any { return f.value }
|
||||||
|
|
||||||
|
// it does not return fields with free keys, however, this should be obvious
|
||||||
|
// non-struct and non-named map values return unnamed fields
|
||||||
func Fields[T any]() []Field {
|
func Fields[T any]() []Field {
|
||||||
return fieldsReflect[T]()
|
return fieldsReflect[T]()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the list and bool flags are not set because it is not possible if they are defined by the root type
|
||||||
func FieldValues(structure any) []Field {
|
func FieldValues(structure any) []Field {
|
||||||
return fieldValuesReflect(structure)
|
return fieldValuesReflect(structure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BindFields(structure any, values []Field) []Field {
|
func BindFields(structure any, values ...Field) []Field {
|
||||||
return nil
|
return bindFieldsReflect(structure, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BindFieldsCreate[T any](values []Field) (any, []Field) {
|
func BindFieldsCreate[T any](values ...Field) (T, []Field) {
|
||||||
return nil, nil
|
var t T
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AcceptsScalar[T any]() bool {
|
func AcceptsScalar[T any]() bool {
|
||||||
|
|||||||
10
notes.txt
10
notes.txt
@ -1,3 +1,7 @@
|
|||||||
maps with string keys and scalar, or list of scalar, values can be supported
|
add time and duration support
|
||||||
fields being set by name, can be converted first to use path
|
don't change the input value when bind returns false
|
||||||
add time and duration support. Can there be any other important quasi-scalars?
|
track down the cases when reflect can panic
|
||||||
|
test:
|
||||||
|
- repeated bindings
|
||||||
|
- cases of allocations
|
||||||
|
- preallocated and unallocated list sizes
|
||||||
|
|||||||
@ -65,12 +65,12 @@ func bindScalarCreate(t reflect.Type, values []any) (reflect.Value, bool) {
|
|||||||
|
|
||||||
func bindScalarReflect(receiver any, values []any) bool {
|
func bindScalarReflect(receiver any, values []any) bool {
|
||||||
v := reflect.ValueOf(receiver)
|
v := reflect.ValueOf(receiver)
|
||||||
if hasCircularReference(nil, v) {
|
if hasCircularReference(v) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vi := range values {
|
for _, vi := range values {
|
||||||
if hasCircularReference(nil, reflect.ValueOf(vi)) {
|
if hasCircularReference(reflect.ValueOf(vi)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,13 +80,13 @@ func bindScalarReflect(receiver any, values []any) bool {
|
|||||||
|
|
||||||
func bindScalarCreateReflect[T any](values []any) (T, bool) {
|
func bindScalarCreateReflect[T any](values []any) (T, bool) {
|
||||||
t := reflect.TypeFor[T]()
|
t := reflect.TypeFor[T]()
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
var tt T
|
var tt T
|
||||||
return tt, false
|
return tt, false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vi := range values {
|
for _, vi := range values {
|
||||||
if hasCircularReference(nil, reflect.ValueOf(vi)) {
|
if hasCircularReference(reflect.ValueOf(vi)) {
|
||||||
var tt T
|
var tt T
|
||||||
return tt, false
|
return tt, false
|
||||||
}
|
}
|
||||||
|
|||||||
77
type.go
77
type.go
@ -11,6 +11,8 @@ const (
|
|||||||
iface
|
iface
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var typeCirc = make(map[reflect.Type]bool)
|
||||||
|
|
||||||
func (f unpackFlag) has(v unpackFlag) bool {
|
func (f unpackFlag) has(v unpackFlag) bool {
|
||||||
return f&v > 0
|
return f&v > 0
|
||||||
}
|
}
|
||||||
@ -25,7 +27,7 @@ func setVisited[T comparable](visited map[T]bool, k T) map[T]bool {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasCircularType(visited map[reflect.Type]bool, t reflect.Type) bool {
|
func checkHasCircularType(visited map[reflect.Type]bool, t reflect.Type) bool {
|
||||||
if visited[t] {
|
if visited[t] {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -33,11 +35,11 @@ func hasCircularType(visited map[reflect.Type]bool, t reflect.Type) bool {
|
|||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Pointer, reflect.Slice:
|
case reflect.Pointer, reflect.Slice:
|
||||||
visited = setVisited(visited, t)
|
visited = setVisited(visited, t)
|
||||||
return hasCircularType(visited, t.Elem())
|
return checkHasCircularType(visited, t.Elem())
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
visited = setVisited(visited, t)
|
visited = setVisited(visited, t)
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
if hasCircularType(visited, t.Field(i).Type) {
|
if checkHasCircularType(visited, t.Field(i).Type) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,8 +50,22 @@ func hasCircularType(visited map[reflect.Type]bool, t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
func hasCircularType(t reflect.Type) bool {
|
||||||
if hasCircularType(nil, v.Type()) {
|
if has, cached := typeCirc[t]; cached {
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
has := checkHasCircularType(nil, t)
|
||||||
|
typeCirc[t] = has
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasCircularType(v.Type()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +77,7 @@ func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visited = setVisited(visited, v.Pointer())
|
visited = setVisited(visited, v.Pointer())
|
||||||
return hasCircularReference(visited, v.Elem())
|
return checkHasCircularReference(visited, v.Elem())
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
p := v.Pointer()
|
p := v.Pointer()
|
||||||
if visited[p] {
|
if visited[p] {
|
||||||
@ -70,7 +86,7 @@ func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
|||||||
|
|
||||||
visited = setVisited(visited, v.Pointer())
|
visited = setVisited(visited, v.Pointer())
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
if hasCircularReference(visited, v.Index(i)) {
|
if checkHasCircularReference(visited, v.Index(i)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,10 +97,10 @@ func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasCircularReference(visited, v.Elem())
|
return checkHasCircularReference(visited, v.Elem())
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
if hasCircularReference(visited, v.Field(i)) {
|
if checkHasCircularReference(visited, v.Field(i)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,6 +111,10 @@ func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasCircularReference(v reflect.Value) bool {
|
||||||
|
return checkHasCircularReference(nil, v)
|
||||||
|
}
|
||||||
|
|
||||||
func isAny(t reflect.Type) bool {
|
func isAny(t reflect.Type) bool {
|
||||||
return t.Kind() == reflect.Interface && t.NumMethod() == 0
|
return t.Kind() == reflect.Interface && t.NumMethod() == 0
|
||||||
}
|
}
|
||||||
@ -153,6 +173,10 @@ func unpackType(t reflect.Type, unpack unpackFlag) reflect.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value {
|
func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value {
|
||||||
|
if v.IsZero() {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
if unpack.has(pointer) && v.Kind() == reflect.Pointer {
|
if unpack.has(pointer) && v.Kind() == reflect.Pointer {
|
||||||
return unpackValue(v.Elem(), unpack)
|
return unpackValue(v.Elem(), unpack)
|
||||||
}
|
}
|
||||||
@ -161,11 +185,11 @@ func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value {
|
|||||||
return unpackValue(v.Index(0), unpack)
|
return unpackValue(v.Index(0), unpack)
|
||||||
}
|
}
|
||||||
|
|
||||||
if unpack.has(anytype) && isAny(v.Type()) && !v.IsNil() {
|
if unpack.has(anytype) && isAny(v.Type()) && !v.IsZero() && !v.IsNil() {
|
||||||
return unpackValue(v.Elem(), unpack)
|
return unpackValue(v.Elem(), unpack)
|
||||||
}
|
}
|
||||||
|
|
||||||
if unpack.has(iface) && isInterface(v.Type()) && !v.IsNil() {
|
if unpack.has(iface) && isInterface(v.Type()) && !v.IsZero() && !v.IsNil() {
|
||||||
return unpackValue(v.Elem(), unpack)
|
return unpackValue(v.Elem(), unpack)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +220,7 @@ func bindable(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptsScalarChecked(t reflect.Type) bool {
|
func acceptsScalarChecked(t reflect.Type) bool {
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +228,7 @@ func acceptsScalarChecked(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptsFieldsChecked(t reflect.Type) bool {
|
func acceptsFieldsChecked(t reflect.Type) bool {
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +236,7 @@ func acceptsFieldsChecked(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptsListChecked(t reflect.Type) bool {
|
func acceptsListChecked(t reflect.Type) bool {
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +244,7 @@ func acceptsListChecked(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bindableChecked(t reflect.Type) bool {
|
func bindableChecked(t reflect.Type) bool {
|
||||||
if hasCircularType(nil, t) {
|
if hasCircularType(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,11 +273,15 @@ func allocate(t reflect.Type, len int) (reflect.Value, bool) {
|
|||||||
return reflect.Zero(t), false
|
return reflect.Zero(t), false
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAny(t) || isScalar(t) || t.Kind() == reflect.Struct || isScalarMap(t) {
|
if isAny(t) || isScalar(t) || t.Kind() == reflect.Struct {
|
||||||
p := reflect.New(t)
|
p := reflect.New(t)
|
||||||
return p.Elem(), len == 1
|
return p.Elem(), len == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isScalarMap(t) {
|
||||||
|
return reflect.MakeMap(t), len == 1
|
||||||
|
}
|
||||||
|
|
||||||
if t.Kind() == reflect.Slice {
|
if t.Kind() == reflect.Slice {
|
||||||
l := reflect.MakeSlice(t, len, len)
|
l := reflect.MakeSlice(t, len, len)
|
||||||
for i := 0; i < len; i++ {
|
for i := 0; i < len; i++ {
|
||||||
@ -268,13 +296,16 @@ func allocate(t reflect.Type, len int) (reflect.Value, bool) {
|
|||||||
return l, true
|
return l, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be pointer
|
if t.Kind() == reflect.Pointer {
|
||||||
e, ok := allocate(t.Elem(), len)
|
e, ok := allocate(t.Elem(), len)
|
||||||
if !ok {
|
if !ok {
|
||||||
return reflect.Zero(t), false
|
return reflect.Zero(t), false
|
||||||
|
}
|
||||||
|
|
||||||
|
p := reflect.New(t.Elem())
|
||||||
|
p.Elem().Set(e)
|
||||||
|
return p, true
|
||||||
}
|
}
|
||||||
|
|
||||||
p := reflect.New(t.Elem())
|
return reflect.Zero(t), false
|
||||||
p.Elem().Set(e)
|
|
||||||
return p, true
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user